Payments & Deposits(Rounding Problem). Fixed.
[fa-stable.git] / includes / ui / items_cart.inc
index a5e15fdbf4a524d1c7ded60d06f48ed4b6a75372..7de50277ff2d9fcc40104c0d0365064c8451f800 100644 (file)
@@ -23,18 +23,31 @@ class items_cart
        var $from_loc;
        var $to_loc;
        var $tran_date;
+       var $doc_date;
+       var $event_date;
        var $transfer_type;
        var $increase;
        var $memo_;
-       var $person_id;
        var $branch_id;
        var $reference;
        var $original_amount;
-       
-       function items_cart($type)
+       var $currency;
+       var $rate;
+       var $source_ref;
+       var $vat_category;
+
+       var $tax_info;  // tax info for the GL transaction
+
+       var $fixed_asset;
+
+       function __construct($type, $trans_no=0)
        {
                $this->trans_type = $type;
+               $this->order_id = $trans_no;
                $this->clear_items();
+               if (in_array($type, array(ST_LOCTRANSFER, ST_INVADJUST, ST_COSTUPDATE, ST_MANUISSUE, ST_MANURECEIVE, ST_JOURNAL)))
+                       $this->currency = get_company_pref('curr_default');
+               $this->rate = 1;
        }
 
        // --------------- line item functions
@@ -84,23 +97,37 @@ class items_cart
 
        function check_qoh($location, $date_, $reverse=false)
        {
-               foreach ($this->line_items as $line_no => $line_item)
-               {
-                       $item_ret = $line_item->check_qoh($location, $date_, $reverse);
-                       if ($item_ret != null)
-                               return $line_no;
+               global $SysPrefs;
+
+               $low_stock = array();
+
+       if (!$SysPrefs->allow_negative_stock())
+       {
+                       foreach ($this->line_items as $line_no => $line_item)
+                               if (has_stock_holding($line_item->mb_flag) || is_fixed_asset($line_item->mb_flag))
+                               {
+                                       $quantity = $line_item->quantity;
+                                       if ($reverse)
+                                               $quantity = -$line_item->quantity;
+
+                                       if ($quantity >= 0)
+                                               continue;
+
+                                       if (check_negative_stock($line_item->stock_id, $quantity, $location, $date_))
+                                               $low_stock[] = $line_item->stock_id;
+                       }
                }
-               return -1;
+               return $low_stock;
        }
 
        // ----------- GL item functions
 
-       function add_gl_item($code_id, $dimension_id, $dimension2_id, $amount, $reference, $description=null)
+       function add_gl_item($code_id, $dimension_id, $dimension2_id, $amount, $memo='', $act_descr=null, $person_id=null, $date=null)
        {
                if (isset($code_id) && $code_id != "" && isset($amount) && isset($dimension_id)  &&
                        isset($dimension2_id))
                {
-                       $this->gl_items[] = new gl_item($code_id, $dimension_id, $dimension2_id, $amount, $reference, $description);
+                       $this->gl_items[] = new gl_item($code_id, $dimension_id, $dimension2_id, $amount, $memo, $act_descr, $person_id, $date);
                        return true;
                }
                else
@@ -112,17 +139,30 @@ class items_cart
                return false;
        }
 
-       function update_gl_item($index, $code_id, $dimension_id, $dimension2_id, $amount, $reference, $description=null)
+       function update_gl_item($index, $code_id, $dimension_id, $dimension2_id, $amount, $memo='', $act_descr=null, $person_id=null)
        {
            $this->gl_items[$index]->code_id = $code_id;
+           $this->gl_items[$index]->person_id = $person_id;
+
+        $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);
+                       $this->gl_items[$index]->person_name = $data['name'];
+                       $this->gl_items[$index]->branch_id = $data['id'];
+               } else
+               {
+                       $this->gl_items[$index]->person_type_id = $this->gl_items[$index]->person_name = '';
+               }
                $this->gl_items[$index]->dimension_id = $dimension_id;
                $this->gl_items[$index]->dimension2_id = $dimension2_id;
                $this->gl_items[$index]->amount = $amount;
-               $this->gl_items[$index]->reference = $reference;
-               if ($description == null)
+               $this->gl_items[$index]->reference = $memo;
+               if ($act_descr == null)
                        $this->gl_items[$index]->description = get_gl_account_name($code_id);
                else
-                       $this->gl_items[$index]->description = $description;
+                       $this->gl_items[$index]->description = $act_descr;
 
        }
 
@@ -177,6 +217,239 @@ class items_cart
                $this->gl_items = array();
 
        }
+       //
+       //      Check if cart contains virtual subaccount (AP/AR) postings
+       //
+       function has_sub_accounts()
+       {
+               foreach ($this->gl_items as $gl_item)
+               {
+                       if (is_subledger_account($gl_item->code_id))
+                               return true;
+               }
+               return false;
+       }
+
+       //
+       //      Check if cart contains postings to tax accounts
+       //
+       function has_taxes()
+       {
+               foreach ($this->gl_items as $gl_item)
+               {
+                       if (is_tax_account($gl_item->code_id))
+                               return true;
+               }
+               return false;
+       }
+
+       /*
+               Collect tax info from the GL transaction lines and return as array of values:
+                       'tax_date'              - tax date
+                       'tax_group'             - related counterparty tax group
+                       'tax_category'  - tax category (not set for now)
+                       'net_amount' - tax amounts array indexed by tax type id
+                       'tax_in', 'tax_out' - tax amounts array indexed by tax type id
+                       'tax_reg' - tax register used
+       */
+       function collect_tax_info()
+       {
+               $tax_info = array();
+               $subledger_sum = $net_sum = 0;
+
+               $tax_info['tax_date'] = $this->tran_date;
+               $vat_percent = get_company_pref('partial_vat_percent');
+               $factor = $vat_percent && ($this->vat_category == VC_PARTIAL) ? $vat_percent/100: 1;
+
+               foreach($this->gl_items as $gl)
+               {
+            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;
+
+                               if ($tax_info['person_type'] == PT_CUSTOMER)
+                               {
+                                       $branch = get_default_branch($gl->person_id);
+                                       $tax_info['tax_group'] = $branch['tax_group_id'];
+                               } else {
+                                       $supplier = get_supplier($gl->person_id);
+                                       $tax_info['tax_group'] = $supplier['tax_group_id'];
+                               }
+                               $subledger_sum += $gl->amount;
+                       } elseif ($tax_id = is_tax_account($gl->code_id))
+                       {
+                               $tax_type = get_tax_type($tax_id);
+                               if ($gl->code_id == $tax_type['purchasing_gl_code']) {
+                                       if (!isset($tax_info['tax_in'][$tax_id]))
+                                               $tax_info['tax_in'][$tax_id] = 0;
+                                       $tax_info['tax_in'][$tax_id] += $gl->amount;
+                                       $tax_info['tax_reg'] = TR_INPUT;
+                               } else {
+                                       if (!isset($tax_info['tax_out'][$tax_id]))
+                                               $tax_info['tax_out'][$tax_id] = 0;
+                                       $tax_info['tax_out'][$tax_id] -= $gl->amount;
+                                       if (!isset($tax_info['tax_reg'])) // TR_INPUT has priority (EU are posted on both accounts)
+                                               $tax_info['tax_reg'] = TR_OUTPUT;
+                               }
+                               if ($tax_type['rate'])
+                               {
+                                       // assume transaction adjustment for negative tax in/out
+                                       $sign = (@$tax_info['tax_in'][$tax_id] < 0 || @$tax_info['tax_out'][$tax_id] < 0) ? -1 : 1;
+                                       // we can have both input and output tax postings in some cases like intra-EU trade.
+                                       // so just calculate net_amount from the higher in/out tax
+                                       $tax_info['net_amount'][$tax_id]
+                                               = $sign*round2(max(abs(@$tax_info['tax_in'][$tax_id]), abs(@$tax_info['tax_out'][$tax_id]))/$tax_type['rate']*100, user_price_dec())/$factor;
+
+                               }
+                       } else
+                               $net_sum += $gl->amount;
+               }
+               // if no tax amount posted guess register type from person_type used (e.g. export invoice)
+               if (!isset($tax_info['tax_reg']) && isset($tax_info['person_type']))
+                       $tax_info['tax_reg'] = $tax_info['person_type']==PT_CUSTOMER ? TR_OUTPUT : TR_INPUT;
+
+               if (count_array(@$tax_info['net_amount']))      // guess exempt sales/purchase if any tax has been found
+               {
+                       $ex_net = abs($net_sum) - @array_sum($tax_info['net_amount']);
+                       if ($ex_net != 0)
+                               $tax_info['net_amount_ex'] = $ex_net;
+               }
+
+               return $tax_info;
+       }
+
+       function set_currency($curr, $rate=0)
+       {
+               $this->currency = $curr;
+               $this->rate = $rate;
+       }
+
+       /*
+               Reduce number of necessary gl posting lines.
+       */
+       function reduce_gl()
+       {
+               /* reduce additional postings */
+               $codes = array();
+               foreach($this->gl_items as $n => $gl)
+               {
+                       $prev = @$codes[$gl->code_id][$gl->person_id][$gl->dimension_id][$gl->dimension2_id][$gl->reference];
+                       if (isset($prev)) { // add amount to previous line for the same gl_code dims and memo
+                               $this->gl_items[$prev]->amount += $gl->amount;
+                               if ($this->gl_items[$prev]->amount == 0) // discard if overall amount==0
+                               {
+                                       unset($this->gl_items[$prev], $codes[$gl->code_id][$gl->person_id][$gl->dimension_id][$gl->dimension2_id][$gl->reference]);
+                               }
+                               unset($this->gl_items[$n]);
+                       } else
+                               $codes[$gl->code_id][$gl->person_id][$gl->dimension_id][$gl->dimension2_id][$gl->reference] = $n;
+               }
+       }
+       /*
+               Write transaction GL postings, creating tax records and updating AP/AR/bank ledger if needed.
+       */
+       function write_gl($check_balance = true)
+       {
+               $delta = $this->gl_items_total();
+               if ($check_balance && floatcmp($delta, 0) !=0)
+               {
+                       $this->add_gl_item(get_company_pref($delta>0 ? 'rounding_db_act' : 'rounding_cr_act'),
+                               0, 0, -$delta, '');
+                       error_log(sprintf( _("Rounding error %s encountered for trans_type:%s,trans_no:%s"), $delta, $this->trans_type, $this->order_id));
+               }
+
+               $bank_trans = $supp_trans = $cust_trans = array();
+               $total_gl = 0;
+               foreach($this->gl_items as $gl)
+               {
+                       if (!isset($gl->date))
+                               $gl->date = $this->tran_date;
+
+                       $total_gl += add_gl_trans($this->trans_type, $this->order_id, $gl->date, $gl->code_id, $gl->dimension_id, $gl->dimension2_id, 
+                               $gl->reference, $gl->amount, $this->currency, $gl->person_type_id, $gl->person_id, "", $this->rate);
+
+                       // post to first found bank account using given gl acount code.
+                       $is_bank_to = is_bank_account($gl->code_id);
+               if ($is_bank_to && (get_bank_account_currency($is_bank_to) == $this->currency)) // do not register exchange variations in bank trans
+               {
+                       if (!isset($bank_trans[$is_bank_to]))
+                               $bank_trans[$is_bank_to] = 0;
+
+                       $bank_trans[$is_bank_to] += $gl->amount;
+               } elseif ($gl->person_id)
+               {
+                       $home_currency = get_company_currency();
+                               // collect per counterparty amounts (in case more than one posting was done to the account),
+                               // do not post exchange variations to AR/AP (journal in not customer/supplier currency)
+                       if ($gl->person_type_id==PT_SUPPLIER && (get_supplier_currency($gl->person_id) == $this->currency || $this->currency != $home_currency))
+                                       $supp_trans[$gl->person_id] = @$supp_trans[$gl->person_id] + $gl->amount;
+                       elseif ($gl->person_type_id==PT_CUSTOMER && (get_customer_currency(null, $gl->branch_id) == $this->currency || $this->currency != $home_currency))
+                                       $cust_trans[$gl->branch_id] = @$cust_trans[$gl->branch_id] + $gl->amount;
+               }
+
+               }
+               // post currency roundings if any
+               if ($check_balance && floatcmp($total_gl, 0))
+                       add_gl_trans($this->trans_type, $this->order_id, $this->tran_date, 
+                               get_company_pref($total_gl>0 ? 'rounding_db_act' : 'rounding_cr_act'), 0, 0, _('Exchange rate roundings'), -$total_gl);
+
+               // update bank ledger if used
+               foreach($bank_trans as $bank_id => $amount)
+                       add_bank_trans($this->trans_type, $this->order_id, $bank_id, $this->reference,
+                               $this->tran_date, $amount, 0, "", $this->currency,
+                               "Cannot insert a destination bank transaction");
+
+               // add AP/AR for journal transaction
+               if ($this->trans_type == ST_JOURNAL)
+               {
+                       // update AR
+                       foreach($cust_trans as $branch_id => $amount)
+                               if (floatcmp($amount, 0))
+                                       write_cust_journal($this->trans_type, $this->order_id, $branch_id, $this->tran_date,
+                                               $this->reference, $amount, $this->rate);
+                       // update AP
+                       foreach($supp_trans as $supp_id => $amount)
+                               if (floatcmp($amount, 0))
+                                       write_supp_journal($this->trans_type, $this->order_id, $supp_id, $this->tran_date,
+                                               $this->reference, -$amount, $this->rate, $this->source_ref);
+               }
+
+               // generate tax records for journal transaction
+               if ($this->trans_type == ST_JOURNAL && is_array($this->tax_info))
+               {
+                       foreach($this->tax_info['net_amount'] as $tax_id => $net)
+                       {
+                               if (!$net)
+                                       continue;
+
+                               // in EU VAT system intra-community goods aquisition is posted to both purchasing and sales tax accounts,
+                               // but included only in purchase register. To avoid double registering ELSE is used below!
+                               if (isset($this->tax_info['tax_in'][$tax_id]))
+                               {
+                                       $tax = $this->tax_info['tax_in'][$tax_id];
+                                       $reg = TR_INPUT;
+                               }
+                               elseif (isset($this->tax_info['tax_out'][$tax_id]))
+                               {
+                                       $tax = $this->tax_info['tax_out'][$tax_id];
+                                       $reg = TR_OUTPUT;
+                               }
+                               elseif (isset($this->tax_info['tax_reg'])) // e.g. export
+                               {
+                                       $tax = 0;
+                                       $reg = $this->tax_info['tax_reg'];
+                               } else
+                                       continue;
+
+                               $tax_nominal = $this->tax_info['rate'][$tax_id]/100*$net;
+                               add_trans_tax_details($this->trans_type, $this->order_id,
+                                       $tax_id, $this->tax_info['rate'][$tax_id], 0, $tax_nominal, $net, $this->rate,
+                                       $this->tran_date,
+                                       $this->source_ref, $reg);
+                       }
+               }
+       }
 }
 
 //--------------------------------------------------------------------------------------------
@@ -192,7 +465,7 @@ class line_item
        var $price;
        var $standard_cost;
 
-       function line_item ($stock_id, $qty, $standard_cost=null, $description=null)
+       function __construct($stock_id, $qty, $standard_cost=null, $description=null)
        {
                $item_row = get_item($stock_id);
 
@@ -208,7 +481,7 @@ class line_item
                        $this->item_description = $description;
 
                if ($standard_cost == null)
-                       $this->standard_cost = $item_row["actual_cost"];
+                       $this->standard_cost = $item_row["purchase_cost"];
                else
                        $this->standard_cost = $standard_cost;
 
@@ -217,32 +490,6 @@ class line_item
                //$this->price = $price;
                $this->price = 0;
        }
-
-       function check_qoh($location, $date_, $reverse)
-       {
-               global $SysPrefs;
-               
-       if (!$SysPrefs->allow_negative_stock())
-       {
-                       if (has_stock_holding($this->mb_flag))
-                       {
-                               $quantity = $this->quantity;
-                               if ($reverse)
-                                       $quantity = -$this->quantity;
-
-                               if ($quantity >= 0)
-                                       return null;
-
-                               $qoh = get_qoh_on_date($this->stock_id, $location, $date_);
-                       if ($quantity + $qoh < 0)
-                       {
-                               return $this;
-                       }
-               }
-       }
-
-       return null;
-       }
 }
 
 //---------------------------------------------------------------------------------------
@@ -256,25 +503,36 @@ class gl_item
        var $amount;
        var $reference;
        var $description;
+       var $person_id;
+       var $person_type_id;
+       var $person_name;
+       var $branch_id;
+       var $date;
 
-       function gl_item($code_id, $dimension_id, $dimension2_id, $amount, $reference,
-               $description=null)
+       function __construct($code_id=null, $dimension_id=0, $dimension2_id=0, $amount=0, $memo='',
+               $act_descr=null, $person_id=null, $date=null)
        {
                //echo "adding $index, $code_id, $dimension_id, $amount, $reference<br>";
 
-               if ($description == null)
+               if ($act_descr == null && $code_id)
                        $this->description = get_gl_account_name($code_id);
                else
-                       $this->description = $description;
+                       $this->description = $act_descr;
 
                $this->code_id = $code_id;
+               $this->person_id = $person_id;
+        $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);
+                       $this->person_name = $data['name'];
+                       $this->branch_id = $data['id'];
+               }
                $this->dimension_id = $dimension_id;
                $this->dimension2_id = $dimension2_id;
-               $this->amount = $amount;
-               $this->reference = $reference;
+               $this->amount = round2($amount, user_price_dec());
+               $this->reference = $memo;
+               $this->date = $date;
        }
 }
-
-//---------------------------------------------------------------------------------------
-
-?>