<?php
/**********************************************************************
Copyright (C) FrontAccounting, LLC.
- Released under the terms of the GNU Affero General Public License,
- AGPL, as published by the Free Software Foundation, either version
- 3 of the License, or (at your option) any later version.
+ Released under the terms of the GNU General Public License, GPL,
+ as published by the Free Software Foundation, either version 3
+ of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the License here <http://www.gnu.org/licenses/agpl-3.0.html>.
+ See the License here <http://www.gnu.org/licenses/gpl-3.0.html>.
***********************************************************************/
include_once($path_to_root . "/includes/prefs/sysprefs.inc");
include_once($path_to_root . "/inventory/includes/inventory_db.inc");
var $line_items;
var $gl_items;
- var $gl_item_count;
-
var $order_id;
- var $editing_item, $deleting_item;
-
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;
+ var $currency;
+ var $rate;
+ var $source_ref;
+ var $vat_category;
+
+ var $tax_info; // tax info for the GL transaction
+
+ var $fixed_asset;
- function items_cart($type)
+ 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
else
{
// shouldn't come here under normal circumstances
- display_db_error("unexpected - adding an invalid item or null quantity", "", true);
+ display_error("unexpected - adding an invalid item or null quantity", "", true);
}
return false;
function remove_from_cart($line_no)
{
- unset($this->line_items[$line_no]);
+ array_splice($this->line_items, $line_no, 1);
}
function count_items()
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[$this->gl_item_count] = new gl_item($this->gl_item_count,
- $code_id, $dimension_id, $dimension2_id, $amount, $reference, $description);
- $this->gl_item_count++;
+ $this->gl_items[] = new gl_item($code_id, $dimension_id, $dimension2_id, $amount, $memo, $act_descr, $person_id, $date);
return true;
}
else
{
// shouldn't come here under normal circumstances
- display_db_error("unexpected - invalid parameters in add_gl_item($code_id, $dimension_id, $dimension2_id, $amount,...)", "", true);
+ display_error("unexpected - invalid parameters in add_gl_item($code_id, $dimension_id, $dimension2_id, $amount,...)", "", true);
}
return false;
}
- function update_gl_item($index, $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]->index = $index;
+ $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]->description = $description;
+ $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 = $act_descr;
}
function remove_gl_item($index)
{
- if (isset($index))
- {
- unset($this->gl_items[$index]);
- }
+ array_splice($this->gl_items, $index, 1);
}
function count_gl_items()
unset($this->gl_items);
$this->gl_items = array();
- $this->gl_item_count = 1;
}
+ //
+ // 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'] && $gl->amount > 0) {
+ 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);
+ }
+ }
+ }
}
//--------------------------------------------------------------------------------------------
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);
if ($item_row == null)
- display_db_error("invalid item added to order : $stock_id", "");
+ display_error("invalid item added to order : $stock_id", "");
$this->mb_flag = $item_row["mb_flag"];
$this->units = $item_row["units"];
$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;
//$this->price = $price;
$this->price = 0;
}
-
- function check_qoh($location, $date_, $reverse)
- {
- if (!sys_prefs::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;
- }
}
//---------------------------------------------------------------------------------------
class gl_item
{
- var $index;
var $code_id;
var $dimension_id;
var $dimension2_id;
var $amount;
var $reference;
var $description;
+ var $person_id;
+ var $person_type_id;
+ var $person_name;
+ var $branch_id;
+ var $date;
- function gl_item($index, $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->index = $index;
$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;
}
}
-
-//---------------------------------------------------------------------------------------
-
-?>