From: Janusz Dobrowolski Date: Fri, 12 Jul 2019 18:34:27 +0000 (+0200) Subject: New tax system implementation. X-Git-Url: https://delta.frontaccounting.com/gitweb/?a=commitdiff_plain;h=3178545262f658085cb8a250a2195246e05c8213;p=fa-stable.git New tax system implementation. --- diff --git a/gl/gl_journal.php b/gl/gl_journal.php index e8af7d23..3b8a4b9d 100644 --- a/gl/gl_journal.php +++ b/gl/gl_journal.php @@ -138,9 +138,10 @@ function create_cart($type=0, $trans_no=0) while ($detail = db_fetch($taxes)) { $tax_id = $detail['tax_type_id']; + $cart->vat_category = $tax_info['tax_category'] = $detail['vat_category']; $tax_info['net_amount'][$tax_id] = $detail['net_amount']; // we can two records for the same tax_id, but in this case net_amount is the same $tax_info['tax_date'] = sql2date($detail['tran_date']); - //$tax_info['tax_group'] = $detail['tax_group_id']; + $tax_info['tax_group'] = $detail['tax_group_id']; } if (isset($tax_info['net_amount'])) // guess exempt sales/purchase if any tax has been found @@ -179,6 +180,8 @@ function create_cart($type=0, $trans_no=0) function update_tax_info() { + $_SESSION['journal_items']->vat_category = get_post('tax_category'); + if (!isset($_SESSION['journal_items']->tax_info) || list_updated('tax_category')) $_SESSION['journal_items']->tax_info = $_SESSION['journal_items']->collect_tax_info(); @@ -475,7 +478,8 @@ if (tab_closed('tabs', 'gl')) { $cart = &$_SESSION['journal_items']; $cart->tax_info['tax_date'] = $_POST['tax_date']; - //$cart->tax_info['tax_group'] = $_POST['tax_group']; + $cart->tax_info['tax_category'] = $_POST['tax_category']; + $cart->tax_info['tax_group'] = $_POST['tax_group']; $taxes = get_all_tax_types(); while ($tax = db_fetch($taxes)) { @@ -489,6 +493,9 @@ if (tab_opened('tabs', 'gl')) $_POST['memo_'] = $_SESSION['journal_items']->memo_; } elseif (tab_opened('tabs', 'tax')) { + $_POST['tax_category'] = $_SESSION['journal_items']->vat_category; + $_SESSION['journal_items']->collect_tax_info(); + $_POST['tax_group'] = $_SESSION['journal_items']->tax_info['tax_group']; set_focus('tax_date'); } @@ -549,7 +556,8 @@ tabbed_content_start('tabs', array( br(); start_table(TABLESTYLE2, "width=40%"); date_row(_("VAT date:"), 'tax_date', '', "colspan='3'"); - //tax_groups_list_row(_("Tax group:"), 'tax_group'); + tax_groups_list_row(_("Tax group:"), 'tax_group'); + vat_category_list_row(_("VAT category:"), 'tax_category', null, true, true); end_table(1); start_table(TABLESTYLE2, "width=60%"); diff --git a/gl/includes/db/gl_db_trans.inc b/gl/includes/db/gl_db_trans.inc index 60f0093c..55d1ccc4 100644 --- a/gl/includes/db/gl_db_trans.inc +++ b/gl/includes/db/gl_db_trans.inc @@ -429,7 +429,7 @@ function get_only_budget_trans_from_to($from_date, $to_date, $account, $dimensio //-------------------------------------------------------------------------------- // Stores journal/bank transaction tax details if applicable // -function add_gl_tax_details($gl_code, $trans_type, $trans_no, $amount, $ex_rate, $date, $memo, $included=0, $net_amount = null) +function add_gl_tax_details($gl_code, $trans_type, $trans_no, $amount, $ex_rate, $date, $memo, $included=0, $net_amount = null, $tax_group_id=null) { $tax_type = is_tax_account($gl_code); if(!$tax_type) return; // $gl_code is not tax account @@ -447,7 +447,7 @@ function add_gl_tax_details($gl_code, $trans_type, $trans_no, $amount, $ex_rate, } } add_trans_tax_details($trans_type, $trans_no, $tax['id'], $tax['rate'], $included, - $amount, $net_amount, $ex_rate, $date, $memo, null); + $amount, $net_amount, $ex_rate, $date, $memo, null, $tax_group_id); } @@ -457,7 +457,7 @@ function add_gl_tax_details($gl_code, $trans_type, $trans_no, $amount, $ex_rate, // actual tax type rate. // function add_trans_tax_details($trans_type, $trans_no, $tax_id, $rate, $included, - $amount, $net_amount, $ex_rate, $tran_date, $memo, $reg_type=null) + $amount, $net_amount, $ex_rate, $tran_date, $memo, $reg_type=null, $tax_group_id=null, $vat_category=0) { // guess tax register if not set if (!isset($reg_type)) @@ -466,14 +466,18 @@ function add_trans_tax_details($trans_type, $trans_no, $tax_id, $rate, $included $sql = "INSERT INTO ".TB_PREF."trans_tax_details (trans_type, trans_no, tran_date, tax_type_id, rate, ex_rate, - included_in_price, net_amount, amount, memo, reg_type) + included_in_price, net_amount, amount, memo, reg_type, tax_group_id, vat_category) VALUES (".db_escape($trans_type)."," . db_escape($trans_no).",'" .date2sql($tran_date)."'," .(int)($tax_id)."," .(float)($rate)."," .(float)($ex_rate).",".($included ? 1:0)."," .db_escape($net_amount)."," - .db_escape($amount).",".db_escape($memo).",".db_escape($reg_type, true).")"; + .db_escape($amount)."," + .db_escape($memo)."," + .db_escape($reg_type, true)."," + .db_escape($tax_group_id)."," + .db_escape($vat_category).")"; db_query($sql, "Cannot save trans tax details"); @@ -524,27 +528,33 @@ function get_tax_summary($from, $to, $also_zero_purchases=false) SUM(IF(trans_type=".ST_CUSTCREDIT.",-1,1)* IF((reg_type=".TR_OUTPUT.")" ." || ((trans_type IN(".ST_SALESINVOICE.",".ST_CUSTCREDIT.") OR (trans_type=".ST_JOURNAL." AND reg_type=".TR_INPUT.")) - ), net_amount*ex_rate,0) + AND (tgroup.tax_area=".TA_EU." OR (tgroup.tax_area=".TA_EXPORT." AND taxrec.vat_category=".VC_SERVICES.") + OR taxrec.vat_category=".VC_REVERSE."))" + .($also_zero_purchases ? '': " AND tax_type_id AND taxrec.rate") + ."), net_amount*ex_rate,0) ) net_output, SUM(IF(trans_type=".ST_CUSTCREDIT.",-1,1)* IF((reg_type=".TR_OUTPUT.")" ." || ((trans_type IN(".ST_SALESINVOICE.",".ST_CUSTCREDIT.") OR (trans_type=".ST_JOURNAL." AND reg_type=".TR_INPUT.")) + AND (tgroup.tax_area=".TA_EU." OR (tgroup.tax_area=".TA_EXPORT." AND taxrec.vat_category=".VC_SERVICES.") + OR taxrec.vat_category=".VC_REVERSE.") ), amount*ex_rate,0)) payable, SUM(IF(trans_type IN(".ST_SUPPCREDIT."),-1,1)* IF(reg_type=".TR_INPUT . ($also_zero_purchases ? '': " AND tax_type_id AND taxrec.rate") - .", net_amount*ex_rate, 0)) net_input, + ." AND vat_category!=".VC_NONDEDUCT.", net_amount*ex_rate, 0)) net_input, SUM(IF(trans_type IN(".ST_SUPPCREDIT."),-1,1)* IF(reg_type=".TR_INPUT . ($also_zero_purchases ? '': " AND tax_type_id AND taxrec.rate ") - .", amount*ex_rate, 0)) collectible, + ." AND vat_category!=".VC_NONDEDUCT.", amount*ex_rate, 0)) collectible, taxrec.rate, ttype.id, ttype.name FROM ".TB_PREF."trans_tax_details taxrec LEFT JOIN ".TB_PREF."tax_types ttype ON taxrec.tax_type_id=ttype.id + LEFT JOIN ".TB_PREF."tax_groups tgroup ON taxrec.tax_group_id=tgroup.id WHERE taxrec.trans_type IN (".implode(',', array(ST_SALESINVOICE, ST_CUSTCREDIT, ST_SUPPINVOICE, ST_SUPPCREDIT, ST_JOURNAL)).") AND taxrec.tran_date >= '$fromdate' diff --git a/includes/session.inc b/includes/session.inc index 06acecce..92bf5482 100644 --- a/includes/session.inc +++ b/includes/session.inc @@ -460,6 +460,7 @@ include_once($path_to_root . "/includes/access_levels.inc"); include_once($path_to_root . "/version.php"); include_once($path_to_root . "/includes/main.inc"); include_once($path_to_root . "/includes/app_entries.inc"); +include_once($path_to_root . "/taxes/tax_rules.inc"); // Ajax communication object $Ajax = new Ajax(); @@ -472,6 +473,7 @@ $Editors = array(); $Pagehelp = array(); $Refs = new references(); +$TS = new tax_system(); // intercept all output to destroy it in case of ajax call register_shutdown_function('end_flush'); diff --git a/includes/types.inc b/includes/types.inc index 959c1cd1..212de03a 100644 --- a/includes/types.inc +++ b/includes/types.inc @@ -246,6 +246,39 @@ define('BO_SUPPLIER', 3); include_once($path_to_root . '/includes/sysnames.inc'); +define('VC_NONE', -1); // for tax adjustemnts +define('VC_OTHER', 0); +define('VC_MEDIA', 1); +define('VC_ASSETS', 2); +define('VC_NONDEDUCT', 3); +define('VC_SERVICES', 4); +define('VC_REVERSE', 5); +define('VC_PARTIAL', 6); + +$vat_categories = array( + VC_OTHER => _('Other goods'), + VC_MEDIA => _('Continous services'), + VC_ASSETS => _('Fixed assets'), + VC_NONDEDUCT => _('No VAT deductible'), + VC_SERVICES => _('Other services'), +// VC_PARTIAL => _('VAT partially deductible'), + VC_REVERSE => _('Reverse charge'), +); + +// +// Tax area categories +// +define('TA_DOMESTIC', 0); +define('TA_EXPORT', 1); +define('TA_EU', 2); + +$tax_area_types = array( + TA_DOMESTIC => _('Domestic'), + TA_EXPORT => _('Abroad'), + TA_EU => _('European Union'), +); + + // tax register type define('TR_OUTPUT', 0); // sales define('TR_INPUT', 1); // purchase diff --git a/includes/ui/items_cart.inc b/includes/ui/items_cart.inc index 44fb1494..416719e1 100644 --- a/includes/ui/items_cart.inc +++ b/includes/ui/items_cart.inc @@ -446,7 +446,7 @@ class items_cart 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); + $this->source_ref, $reg, $this->tax_info['tax_group'], $this->tax_info['tax_category']); } } } diff --git a/includes/ui/ui_lists.inc b/includes/ui/ui_lists.inc index 55ef6777..07202791 100644 --- a/includes/ui/ui_lists.inc +++ b/includes/ui/ui_lists.inc @@ -2568,6 +2568,26 @@ function payment_services($name) )); } +function vat_category_list_row($label, $name, $selected_id=null, $enabled = true, $submit_on_change=false, $show_obsolete=false) +{ + global $vat_categories; + + echo ""; + if ($label != null) + echo "$label\n"; + echo ""; + + $categories = $vat_categories; + if (!$show_obsolete) + unset($categories[VC_MEDIA]); // obsolete category removed to avoid mistake usage + + echo array_selector($name, $selected_id, $categories, + array( 'select_submit'=> $submit_on_change, + 'disabled' => !$enabled)); + echo "\n"; + +} + function tax_algorithm_list($name, $value=null, $submit_on_change = false) { global $tax_algorithms; @@ -2722,3 +2742,20 @@ function collations_list_row($label, $name, $selected_id=null) array('select_submit'=> false) ); echo "\n"; } + +function vat_areas_list_row($label, $name, $selected_id=null, $enabled=true) +{ + global $tax_area_types; + + echo ""; + if ($label != null) + echo "$label\n"; + echo ""; + + echo array_selector($name, $selected_id, $tax_area_types, + array( + 'select_submit'=> true, + 'disabled' => !$enabled) ); + echo "\n"; +} + diff --git a/inventory/includes/db/items_category_db.inc b/inventory/includes/db/items_category_db.inc index 8b6d872a..48984bb6 100644 --- a/inventory/includes/db/items_category_db.inc +++ b/inventory/includes/db/items_category_db.inc @@ -11,12 +11,12 @@ ***********************************************************************/ function add_item_category($description, $tax_type_id, $sales_account, $cogs_account, $inventory_account, $adjustment_account, $wip_account, - $units, $mb_flag, $dim1, $dim2, $no_sale, $no_purchase) + $units, $mb_flag, $dim1, $dim2, $no_sale, $no_purchase, $vat_category) { $sql = "INSERT INTO ".TB_PREF."stock_category (description, dflt_tax_type, dflt_units, dflt_mb_flag, dflt_sales_act, dflt_cogs_act, dflt_inventory_act, dflt_adjustment_act, dflt_wip_act, - dflt_dim1, dflt_dim2, dflt_no_sale, dflt_no_purchase) + dflt_dim1, dflt_dim2, dflt_no_sale, dflt_no_purchase, vat_category) VALUES (" .db_escape($description)."," .db_escape($tax_type_id)."," @@ -30,14 +30,15 @@ function add_item_category($description, $tax_type_id, $sales_account, .db_escape($dim1)."," .db_escape($dim2)."," .db_escape($no_sale)."," - .db_escape($no_purchase).")"; + .db_escape($no_purchase)."," + .db_escape($vat_category).")"; db_query($sql,"an item category could not be added"); } function update_item_category($id, $description, $tax_type_id, $sales_account, $cogs_account, $inventory_account, $adjustment_account, - $wip_account, $units, $mb_flag, $dim1, $dim2, $no_sale, $no_purchase) + $wip_account, $units, $mb_flag, $dim1, $dim2, $no_sale, $no_purchase, $vat_category) { $sql = "UPDATE ".TB_PREF."stock_category SET " @@ -53,7 +54,8 @@ function update_item_category($id, $description, $tax_type_id, ."dflt_dim1 = ".db_escape($dim1)."," ."dflt_dim2 = ".db_escape($dim2)."," ."dflt_no_sale = ".db_escape($no_sale)."," - ."dflt_no_purchase = ".db_escape($no_purchase) + ."dflt_no_purchase = ".db_escape($no_purchase)."," + ."vat_category = ".db_escape($vat_category) ."WHERE category_id = ".db_escape($id); db_query($sql,"an item category could not be updated"); diff --git a/inventory/includes/db/items_db.inc b/inventory/includes/db/items_db.inc index 07fde0d1..cfbbbdbe 100644 --- a/inventory/includes/db/items_db.inc +++ b/inventory/includes/db/items_db.inc @@ -14,7 +14,7 @@ function update_item($stock_id, $description, $long_description, $category_id, $cogs_account, $adjustment_account, $wip_account, $dimension_id, $dimension2_id, $no_sale, $editable, $no_purchase, $depreciation_method = 'D', $depreciation_rate=100, $depreciation_factor=1, - $depreciation_start=null, $fa_class_id=null) + $depreciation_start=null, $fa_class_id=null, $vat_category='') { $sql = "UPDATE ".TB_PREF."stock_master SET long_description=".db_escape($long_description).", description=".db_escape($description).", @@ -41,6 +41,9 @@ function update_item($stock_id, $description, $long_description, $category_id, if ($mb_flag != '') $sql .= ", mb_flag=".db_escape($mb_flag); + if ($vat_category != '') + $sql .= ", vat_category=".db_escape($vat_category); + if (isset($depreciation_start)) { $sql .= ", depreciation_start='".date2sql($depreciation_start)."'" .", depreciation_date='".date2sql($depreciation_start)."'"; @@ -58,11 +61,11 @@ function add_item($stock_id, $description, $long_description, $category_id, $cogs_account, $adjustment_account, $wip_account, $dimension_id, $dimension2_id, $no_sale, $editable, $no_purchase, $depreciation_method='D', $depreciation_rate=100, $depreciation_factor=1, $depreciation_start=null, - $fa_class_id=null) + $fa_class_id=null, $vat_category=0) { $sql = "INSERT INTO ".TB_PREF."stock_master (stock_id, description, long_description, category_id, tax_type_id, units, mb_flag, sales_account, inventory_account, cogs_account, - adjustment_account, wip_account, dimension_id, dimension2_id, no_sale, no_purchase, editable, + adjustment_account, wip_account, dimension_id, dimension2_id, no_sale, no_purchase, editable, vat_category, depreciation_method, depreciation_rate, depreciation_factor" .(isset($depreciation_start) ? ", depreciation_start, depreciation_date, fa_class_id" : "") .") VALUES (".db_escape($stock_id).", ".db_escape($description).", ".db_escape($long_description).", @@ -74,7 +77,7 @@ function add_item($stock_id, $description, $long_description, $category_id, .db_escape($dimension_id).", ".db_escape($dimension2_id)."," .db_escape($no_sale)."," .db_escape($no_purchase)."," - .db_escape($editable)."," + .db_escape($editable).",".db_escape($vat_category)."," .db_escape($depreciation_method).",".db_escape($depreciation_rate).",".db_escape($depreciation_factor) .(isset($depreciation_start) ? ",'".date2sql($depreciation_start)."','".date2sql($depreciation_start)."',".db_escape($fa_class_id) : "") .")"; diff --git a/inventory/manage/item_categories.php b/inventory/manage/item_categories.php index 136c0a80..dfa0a786 100644 --- a/inventory/manage/item_categories.php +++ b/inventory/manage/item_categories.php @@ -57,7 +57,7 @@ if ($Mode=='ADD_ITEM' || $Mode=='UPDATE_ITEM') $_POST['cogs_account'], $_POST['inventory_account'], $_POST['adjustment_account'], $_POST['wip_account'], $_POST['units'], $_POST['mb_flag'], $_POST['dim1'], $_POST['dim2'], - check_value('no_sale'), check_value('no_purchase')); + check_value('no_sale'), check_value('no_purchase'), $_POST['vat_category']); display_notification(_('Selected item category has been updated')); } else @@ -67,7 +67,7 @@ if ($Mode=='ADD_ITEM' || $Mode=='UPDATE_ITEM') $_POST['cogs_account'], $_POST['inventory_account'], $_POST['adjustment_account'], $_POST['wip_account'], $_POST['units'], $_POST['mb_flag'], $_POST['dim1'], - $_POST['dim2'], check_value('no_sale'), check_value('no_purchase')); + $_POST['dim2'], check_value('no_sale'), check_value('no_purchase'), $_POST['vat_category']); display_notification(_('New item category has been added')); } $Mode = 'RESET'; @@ -118,7 +118,7 @@ if ($fixed_asset) { _("Asset Account"), _("Deprecation Cost Account"), _("Depreciation/Disposal Account"), "", ""); } else { - $th = array(_("Name"), _("Tax type"), _("Units"), _("Type"), _("Sales Act"), + $th = array(_("Name"), _("Tax type"), _("Units"), _("VAT Category"), _("Type"), _("Sales Act"), _("Inventory Account"), _("COGS Account"), _("Adjustment Account"), _("Assembly Account"), "", ""); } @@ -135,8 +135,10 @@ while ($myrow = db_fetch($result)) label_cell($myrow["description"]); label_cell($myrow["tax_name"]); label_cell($myrow["dflt_units"], "align=center"); - if (!$fixed_asset) + if (!$fixed_asset) { + label_cell($vat_categories[$myrow["vat_category"]]); label_cell($stock_types[$myrow["dflt_mb_flag"]]); + } label_cell($myrow["dflt_sales_act"], "align=center"); label_cell($myrow["dflt_inventory_act"], "align=center"); label_cell($myrow["dflt_cogs_act"], "align=center"); @@ -177,6 +179,7 @@ if ($selected_id != -1) $_POST['dim2'] = $myrow["dflt_dim2"]; $_POST['no_sale'] = $myrow["dflt_no_sale"]; $_POST['no_purchase'] = $myrow["dflt_no_purchase"]; + $_POST['vat_category'] = $myrow["vat_category"]; } hidden('selected_id', $selected_id); hidden('category_id'); @@ -218,6 +221,11 @@ else stock_units_list_row(_("Units of Measure:"), 'units', null); +if (is_fixed_asset($_POST['mb_flag'])) + hidden('vat_category', VC_ASSETS); +else + vat_category_list_row(_("VAT category:"), 'vat_category',null, true, false, $selected_id!=-1); + if (is_fixed_asset($_POST['mb_flag'])) hidden('no_sale', 0); else diff --git a/inventory/manage/items.php b/inventory/manage/items.php index dfaa25a0..352a2fbf 100644 --- a/inventory/manage/items.php +++ b/inventory/manage/items.php @@ -238,7 +238,7 @@ if (isset($_POST['addupdate'])) $_POST['dimension_id'], $_POST['dimension2_id'], check_value('no_sale'), check_value('editable'), check_value('no_purchase'), get_post('depreciation_method'), input_num('depreciation_rate'), input_num('depreciation_factor'), get_post('depreciation_start', null), - get_post('fa_class_id')); + get_post('fa_class_id'), get_post('vat_category')); update_record_status($_POST['NewStockID'], $_POST['inactive'], 'stock_master', 'stock_id'); @@ -259,7 +259,7 @@ if (isset($_POST['addupdate'])) $_POST['dimension_id'], $_POST['dimension2_id'], check_value('no_sale'), check_value('editable'), check_value('no_purchase'), get_post('depreciation_method'), input_num('depreciation_rate'), input_num('depreciation_factor'), get_post('depreciation_start', null), - get_post('fa_class_id')); + get_post('fa_class_id'), get_post('vat_category')); display_notification(_("A new item has been added.")); $_POST['stock_id'] = $_POST['NewStockID'] = @@ -314,6 +314,44 @@ if (isset($_POST['delete']) && strlen($_POST['delete']) > 1) } } +function generateBarcode() { + $tmpBarcodeID = ""; + $tmpCountTrys = 0; + while ($tmpBarcodeID == "") { + srand ((double) microtime( )*1000000); + $random_1 = rand(1,9); + $random_2 = rand(0,9); + $random_3 = rand(0,9); + $random_4 = rand(0,9); + $random_5 = rand(0,9); + $random_6 = rand(0,9); + $random_7 = rand(0,9); + //$random_8 = rand(0,9); + + // http://stackoverflow.com/questions/1136642/ean-8-how-to-calculate-checksum-digit + $sum1 = $random_2 + $random_4 + $random_6; + $sum2 = 3 * ($random_1 + $random_3 + $random_5 + $random_7 ); + $checksum_value = $sum1 + $sum2; + + $checksum_digit = 10 - ($checksum_value % 10); + if ($checksum_digit == 10) + $checksum_digit = 0; + + $random_8 = $checksum_digit; + + $tmpBarcodeID = $random_1 . $random_2 . $random_3 . $random_4 . $random_5 . $random_6 . $random_7 . $random_8; + + // LETS CHECK TO SEE IF THIS NUMBER HAS EVER BEEN USED + $query = "SELECT stock_id FROM ".TB_PREF."stock_master WHERE stock_id='" . $tmpBarcodeID . "'"; + $arr_stock = db_fetch(db_query($query)); + + if ( !$arr_stock['stock_id'] ) { + return $tmpBarcodeID; + } + $tmpBarcodeID = ""; + } +} + function item_settings(&$stock_id, $new_item) { global $SysPrefs, $path_to_root, $page_nested, $depreciation_methods; @@ -390,6 +428,8 @@ function item_settings(&$stock_id, $new_item) stock_units_list_row(_('Units of Measure:'), 'units', null, $fresh_item); + vat_category_list_row(_("VAT category:"), 'vat_category', null, $fresh_item, false, !$new_item); + check_row(_("Editable description:"), 'editable'); if (get_post('fixed_asset')) @@ -641,44 +681,4 @@ if (get_post('fixed_asset')) end_form(); -//------------------------------------------------------------------------------------ - end_page(); - -function generateBarcode() { - $tmpBarcodeID = ""; - $tmpCountTrys = 0; - while ($tmpBarcodeID == "") { - srand ((double) microtime( )*1000000); - $random_1 = rand(1,9); - $random_2 = rand(0,9); - $random_3 = rand(0,9); - $random_4 = rand(0,9); - $random_5 = rand(0,9); - $random_6 = rand(0,9); - $random_7 = rand(0,9); - //$random_8 = rand(0,9); - - // http://stackoverflow.com/questions/1136642/ean-8-how-to-calculate-checksum-digit - $sum1 = $random_2 + $random_4 + $random_6; - $sum2 = 3 * ($random_1 + $random_3 + $random_5 + $random_7 ); - $checksum_value = $sum1 + $sum2; - - $checksum_digit = 10 - ($checksum_value % 10); - if ($checksum_digit == 10) - $checksum_digit = 0; - - $random_8 = $checksum_digit; - - $tmpBarcodeID = $random_1 . $random_2 . $random_3 . $random_4 . $random_5 . $random_6 . $random_7 . $random_8; - - // LETS CHECK TO SEE IF THIS NUMBER HAS EVER BEEN USED - $query = "SELECT stock_id FROM ".TB_PREF."stock_master WHERE stock_id='" . $tmpBarcodeID . "'"; - $arr_stock = db_fetch(db_query($query)); - - if ( !$arr_stock['stock_id'] ) { - return $tmpBarcodeID; - } - $tmpBarcodeID = ""; - } -} diff --git a/purchasing/includes/db/grn_db.inc b/purchasing/includes/db/grn_db.inc index e66c0910..886e05fc 100644 --- a/purchasing/includes/db/grn_db.inc +++ b/purchasing/includes/db/grn_db.inc @@ -25,7 +25,8 @@ function update_average_material_cost($supplier, $stock_id, $price, $qty, $date, $currency = null; if ($supp['tax_included']) - $price = get_tax_free_price_for_item($stock_id, $price, $supp['tax_group_id'], + $price = get_tax_free_price_for_item(ST_SUPPINVOICE, // ? + $stock_id, $price, $supp['tax_group_id'], $supp['tax_included']); if ($currency != null) diff --git a/purchasing/includes/db/invoice_db.inc b/purchasing/includes/db/invoice_db.inc index 427b73dc..e2da684c 100644 --- a/purchasing/includes/db/invoice_db.inc +++ b/purchasing/includes/db/invoice_db.inc @@ -123,247 +123,243 @@ function get_diff_in_home_currency($supplier, $old_date, $date, $amount1, $amoun } //---------------------------------------------------------------------------------------- -function add_supp_invoice(&$supp_trans) +function add_supp_invoice(&$supp_trans) //, $already_voided=false, $allocs=null) { - global $Refs; + global $Refs, $systypes_array; - //$company_currency = get_company_currency(); $trans_no = $supp_trans->trans_no; $trans_type = $supp_trans->trans_type; $supplier = get_supplier($supp_trans->supplier_id); + $dec = user_price_dec(); begin_transaction(); hook_db_prewrite($supp_trans, $trans_type); - $tax_total = 0; - $taxes = $supp_trans->get_taxes($supp_trans->tax_group_id); - if ($trans_no) { + + if ($trans_no) { // void old transaction +// if (!$already_voided) { // transaction is already voided in case of direct invoice edition, which is needed for proper inventory value handling $allocs = get_payments_for($trans_no, $trans_type, $supp_trans->supplier_id); // save allocations - void_transaction($trans_type, $trans_no, Today(), _("Document reentered.")); + void_supp_invoice($trans_type, $trans_no, true); + add_audit_trail($trans_type, $trans_no, Today(), _("Voided.")); + add_voided_entry($trans_type, $trans_no, Today(), _("Document reentered.")); $Refs->restore_last($trans_type, $trans_no); +// } } else $allocs = get_po_prepayments($supp_trans); - add_new_exchange_rate($supp_trans->currency, $supp_trans->tran_date, $supp_trans->ex_rate); - - foreach ($taxes as $n => $taxitem) - { - $taxes[$n]['Value'] = round2($taxitem['Value'], user_price_dec()); - $tax_total += $taxes[$n]['Value']; - } - - $invoice_items_total = $supp_trans->get_items_total(); - - $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()); - } + // register exchange rate when used first time on date + add_new_exchange_rate($supplier['curr_code'], $supp_trans->tran_date, $supp_trans->ex_rate); - if ($trans_type == ST_SUPPCREDIT) - { - // let's negate everything because it's a credit note - $invoice_items_total = -$invoice_items_total; - $tax_total = -$tax_total; - $supp_trans->ov_discount = -$supp_trans->ov_discount; // this isn't used at all... - $item_added_tax = -$item_added_tax; - } - - $date_ = $supp_trans->tran_date; - $ex_rate = get_exchange_rate_from_home_currency($supp_trans->currency, $date_); - - /*First insert the invoice into the supp_trans table*/ - $invoice_id = write_supp_trans($trans_type, 0, $supp_trans->supplier_id, $date_, $supp_trans->due_date, - $supp_trans->reference, $supp_trans->supp_reference, - $invoice_items_total, $item_added_tax, $supp_trans->ov_discount, "", 0, $supp_trans->tax_included); + // store basic transaction info + $invoice_id = write_supp_trans($trans_type, 0, $supp_trans->supplier_id, $supp_trans->tran_date, $supp_trans->due_date, + $supp_trans->reference, $supp_trans->supp_reference, 0, 0, 0, "", $supp_trans->ex_rate, $supp_trans->tax_included); if ($trans_no) move_trans_attachments($trans_type, $trans_no, $invoice_id); $supp_trans->trans_no = $invoice_id; + $date_ = $supp_trans->tran_date; - $total = 0; + if (!$supp_trans->ex_rate) + $supp_trans->ex_rate = get_exchange_rate_from_home_currency($supplier['curr_code'], $date_); - /* Now the TAX account */ - $taxes = $supp_trans->get_taxes($supp_trans->tax_group_id, 0, false); - $net_diff = 0; + // prepare cart for GL postings + $gl_cart = new items_cart($trans_type, $invoice_id); + $gl_cart->tran_date = $date_; + $gl_cart->set_currency($supplier['curr_code'], $supp_trans->ex_rate); - foreach ($taxes as $taxitem) - { - if ($taxitem['Net'] != 0) - { - if (isset($taxitem['Override'])) { - if ($supp_trans->tax_included) { // if tax included, fix net amount to preserve overall line price - $net_diff += $taxitem['Override'] - $taxitem['Value']; - $taxitem['Net'] += $taxitem['Override'] - $taxitem['Value']; - } - $taxitem['Value'] = $taxitem['Override']; - } - add_trans_tax_details($trans_type, $invoice_id, - $taxitem['tax_type_id'], $taxitem['rate'], $supp_trans->tax_included, $taxitem['Value'], - $taxitem['Net'], $ex_rate, $date_, $supp_trans->supp_reference, TR_INPUT); + $clearing_act = get_company_pref('grn_clearing_act'); - if (isset($taxitem['purchasing_gl_code'])) - { - if ($trans_type == ST_SUPPCREDIT) - $taxitem['Value'] = -$taxitem['Value']; - $total += add_gl_trans_supplier($trans_type, $invoice_id, $date_, - $taxitem['purchasing_gl_code'], 0, 0, $taxitem['Value'], - $supp_trans->supplier_id, - "A general ledger transaction for the tax amount could not be added"); - } - } - } - if ($trans_type == ST_SUPPCREDIT) - $net_diff = -$net_diff; + $supp_trans->split_line_values(); // generate amounts for GL postings - /* Now the AP account */ - $total += add_gl_trans_supplier($trans_type, $invoice_id, $date_, $supplier["payable_account"], 0, 0, - -($invoice_items_total + $item_added_tax + $supp_trans->ov_discount), - $supp_trans->supplier_id, - "The general ledger transaction for the control total could not be added"); + $tax_total = 0; + $net_total = 0; + foreach($supp_trans->grn_items as $item) + { + if ($trans_type == ST_SUPPCREDIT) + { + $item->this_quantity_inv = -$item->this_quantity_inv; + set_grn_item_credited($item, $supp_trans->supplier_id, $invoice_id, $date_); + } + $item_gl = $item->gl_amounts; - $to_allocate = ($invoice_items_total + $item_added_tax + $supp_trans->ov_discount); + $stock_gl_code = get_stock_gl_code($item->item_code); + $dimension = !empty($supp_trans->dimension) ? $supp_trans->dimension : + ($supplier['dimension_id'] ? $supplier['dimension_id'] : $stock_gl_code['dimension_id']); + $dimension2 = !empty($supp_trans->dimension2) ? $supp_trans->dimension2 : + ($supplier['dimension2_id'] ? $supplier['dimension2_id'] : $stock_gl_code['dimension2_id']); - foreach ($supp_trans->gl_codes as $entered_gl_code) - { - /*GL Items are straight forward - just do the debit postings to the GL accounts specified - - the credit is to creditors control act done later for the total invoice value + tax*/ + $line_tax = 0; + foreach($item_gl as $index => $value) + { + if (is_numeric($index)) // taxes + { + if ($value['Deductible']) + { // GL: VAT deductible + $gl_cart->add_gl_item($value['purchasing_gl_code'], 0, 0, $value['Deductible']+$value['Adjust'], //FIXME: 'Adjust' ? + $value['Payable'] ? sprintf(_('Internal invoice %s input tax'), $supp_trans->reference) : ''); + // GL: VAT adjustment due to vat factor + if ($value['Adjust']) + $gl_cart->add_gl_item(get_company_pref('tax_adjustments_act'), 0, 0, -$value['Adjust']); + } + if ($value['Payable']) // reverse charge/intracommunity aquisition + { + $gl_cart->add_gl_item($value['sales_gl_code'], 0, 0, -$value['Payable'], + sprintf(_('Internal invoice %s input tax'), $supp_trans->reference)); + } + // GL: AP account (vat part) + if ($value['Value']) + { + $gl_cart->add_gl_item($supplier["payable_account"], 0, 0, -$value['Value'] , '', '', $supp_trans->supplier_id); + } + if (($item->vat_category == VC_REVERSE) && $supplier['tax_area'] == TA_EU) + $vat_category = VC_OTHER; + else + $vat_category = $item->vat_category; - if ($trans_type == ST_SUPPCREDIT) - $entered_gl_code->amount = -$entered_gl_code->amount; + add_trans_tax_details($trans_type, $invoice_id, + $value['tax_type_id'], $value['rate'], $supp_trans->tax_included, $value['Value'], + $item_gl['Net'], $supp_trans->ex_rate, $date_, $supp_trans->supp_reference, TR_INPUT, $supp_trans->tax_group_id, $vat_category); - $memo_ = $entered_gl_code->memo_; - $total += add_gl_trans_supplier($trans_type, $invoice_id, $date_, $entered_gl_code->gl_code, - $entered_gl_code->gl_dim, $entered_gl_code->gl_dim2, $entered_gl_code->amount, $supp_trans->supplier_id, "", 0, $memo_); + // $value['Deductible'], ??? - add_supp_invoice_gl_item($trans_type, $invoice_id, $entered_gl_code->gl_code, $entered_gl_code->amount, $memo_, - $entered_gl_code->gl_dim, $entered_gl_code->gl_dim2); + $tax_total += $value['Value']; - // store tax details if the gl account is a tax account - if ($trans_type == ST_SUPPCREDIT) - $entered_gl_code->amount = -$entered_gl_code->amount; - add_gl_tax_details($entered_gl_code->gl_code, - $trans_type, $invoice_id, $entered_gl_code->amount, - $ex_rate, $date_, $supp_trans->supp_reference, $supp_trans->tax_included); - } + $line_tax += $value['Payable'] ? $value['Payable'] : $value['Value']; + } + } + // GL: AP account (net) + $gl_cart->add_gl_item($supplier["payable_account"], 0, 0, -$item_gl['Net'], '', '', $supp_trans->supplier_id); + $net_total += $item_gl['Net']; - $clearing_act = get_company_pref('grn_clearing_act'); - foreach ($supp_trans->grn_items as $line_no => $entered_grn) - { + // cost line value + $taxfree_line = $item_gl['Cost']; - if ($trans_type == ST_SUPPCREDIT) - { - $entered_grn->this_quantity_inv = -$entered_grn->this_quantity_inv; - set_grn_item_credited($entered_grn, $supp_trans->supplier_id, $invoice_id, $date_); - } + $old = update_supp_received_items_for_invoice($item->id, $item->po_detail_item, + $item->this_quantity_inv, $item->chg_price); - // For tax included pricelist the net price is calculated down from tax_included price. - // To avoid rounding errors we have to operate on line value instead of price - // Additionally we have to take into account differences in net value - // due to variations in tax calculations on supplier side. More over there is no direct relation between - // taxes and sales accounts, so we add net_diff just to first posted net value. This is _ugly_hack_ - // which save us from rewriting whole routine, and works right only for small tax differences. + // Since the standard cost is always calculated on basis of the po unit_price, + // this is also the price that should be the base of calculating the price diff. + // In cases where there is two different po invoices on the same delivery with different unit prices this will not work either + $old_price = $old[2]; - $taxfree_line = get_tax_free_price_for_item($entered_grn->item_code, $entered_grn->this_quantity_inv * $entered_grn->chg_price, - $supp_trans->tax_group_id, $supp_trans->tax_included) - $net_diff; $net_diff = 0; + /* + If statement is removed. Should always check for deliveries nomatter if there has been a price change. + */ + $old_date = sql2date($old[1]); - $line_tax = get_full_price_for_item($entered_grn->item_code, - $entered_grn->this_quantity_inv * $entered_grn->chg_price, 0, $supp_trans->tax_included) - $taxfree_line; + if (!is_inventory_item($item->item_code)) + { + $gl_cart->add_gl_item($supplier["purchase_account"] ? $supplier["purchase_account"] : $stock_gl_code["cogs_account"], $dimension, $dimension2, $taxfree_line); - $stock_gl_code = get_stock_gl_code($entered_grn->item_code); + } else { - $dim = !empty($supp_trans->dimension) ? $supp_trans->dimension : - ($supplier['dimension_id'] ? $supplier['dimension_id'] : $stock_gl_code['dimension_id']); - $dim2 = !empty($supp_trans->dimension2) ? $supp_trans->dimension2 : - ($supplier['dimension2_id'] ? $supplier['dimension2_id'] : $stock_gl_code['dimension2_id']); - if ($trans_type == ST_SUPPCREDIT) - { - $iv_act = (is_inventory_item($entered_grn->item_code) ? $stock_gl_code["inventory_account"] : - ($supplier["purchase_account"] ? $supplier["purchase_account"] : $stock_gl_code["cogs_account"])); - $total += add_gl_trans_supplier($trans_type, $invoice_id, $date_, $iv_act, - $dim, $dim2, $taxfree_line, $supp_trans->supplier_id); - } - else - { - // -------------- if price changed since po received. - $iv_act = is_inventory_item($entered_grn->item_code) ? ($clearing_act ? $clearing_act : $stock_gl_code["inventory_account"]) : - ($supplier["purchase_account"] ? $supplier["purchase_account"] : $stock_gl_code["cogs_account"]); - $old = update_supp_received_items_for_invoice($entered_grn->id, $entered_grn->po_detail_item, - $entered_grn->this_quantity_inv, $entered_grn->chg_price); - - // Since the standard cost is always calculated on basis of the po unit_price, - // this is also the price that should be the base of calculating the price diff. - // In cases where there is two different po invoices on the same delivery with different unit prices this will not work either - - $old_price = $old[2]; - - $old_date = sql2date($old[1]); - if (!is_inventory_item($entered_grn->item_code)) - $total += add_gl_trans_supplier($trans_type, $invoice_id, $date_, $iv_act, - $dim, $dim2, $taxfree_line, $supp_trans->supplier_id); - else - { - $ex_rate = get_exchange_rate_from_home_currency($supp_trans->currency, $old_date); - $old_value = get_tax_free_price_for_item($entered_grn->item_code, $entered_grn->this_quantity_inv * $old_price, - $supp_trans->tax_group_id, $supp_trans->tax_included); + $val = split_item_price($item->item_code, $item->this_quantity_inv * $old_price, + $supp_trans->tax_group_id, $supp_trans->tax_included); - $currency = get_supplier_currency($supp_trans->supplier_id); + $GRN_value = $val['Cost']; - $total += add_gl_trans_supplier($trans_type, $invoice_id, $date_, $iv_act, - $dim, $dim2, $old_value, $supp_trans->supplier_id, "", $ex_rate); - $diff = get_diff_in_home_currency($supp_trans->supplier_id, $old_date, $date_, $old_value, - $taxfree_line); + $diff = get_diff_in_home_currency($supp_trans->supplier_id, $old_date, $date_, $GRN_value, + $taxfree_line); - $mat_cost = update_average_material_cost(null, $entered_grn->item_code, - $diff/$entered_grn->this_quantity_inv, $entered_grn->this_quantity_inv, null, true); + // update average cost with per item part of difference (between invoice value and value set on po/grn) + update_average_material_cost(null, $item->item_code, + $diff/$item->this_quantity_inv, $item->this_quantity_inv, null, true, "add_supp_invoice $trans_type:$invoice_id"); + if ($clearing_act) // no postings on GRN, so post full net value + { //Add GL transaction for GRN Provision in case of difference + // material was already posted due GRN; post differences in value if ($diff != 0) { - $total += add_gl_trans($trans_type, $invoice_id, $date_, $stock_gl_code["inventory_account"], - $dim, $dim2, 'GRN Provision', $diff, null, null, null, - "The general ledger transaction could not be added for the GRN of the inventory item"); + $gl_cart->add_gl_item($stock_gl_code["inventory_account"], // cart is in supplier currency, so need to fix by ex_rate here + $dimension, $dimension2, $diff/$gl_cart->rate, _('GRN Provision')); // subject to rounding errors? + $gl_cart->add_gl_item($clearing_act, + $dimension, $dimension2, -$diff/$gl_cart->rate, _('GRN Provision')); + + //Chaitanya //If QOH is 0 or negative then update_average_material_cost will be skipped //Thus difference in PO and Supp Invoice should be handled separately - $qoh = get_qoh_on_date($entered_grn->item_code); + $qoh = get_qoh_on_date($item->item_code); if ($qoh <= 0) { global $Refs; - + //Chaitanya : Post a journal entry $id = get_next_trans_no(ST_JOURNAL); $ref = $Refs->get_next(ST_JOURNAL, null, $date_); add_journal(ST_JOURNAL, $id, $diff, $date_, get_company_currency(), $ref); - $stock_id = $entered_grn->item_code; + $stock_id = $item->item_code; $stock_gl_code = get_stock_gl_code($stock_id); $memo = _("Supplier invoice adjustment for zero inventory of ").$stock_id." "._("Invoice")." ".$supp_trans->reference; //Reverse the inventory effect if $qoh <=0 add_gl_trans_std_cost(ST_JOURNAL, $id, $date_, $stock_gl_code["inventory_account"], - $dim, $dim2, $memo, -$diff); + $dimension, $dimension2, + $memo, -$diff); //GL Posting to inventory adjustment account add_gl_trans_std_cost(ST_JOURNAL, $id, $date_, $stock_gl_code["adjustment_account"], - $dim, $dim2, $memo, $diff); - - add_audit_trail(ST_JOURNAL, $id, $date_); + $dimension, $dimension2, + $memo, $diff); + add_audit_trail(ST_JOURNAL, $id, $date_); add_comments(ST_JOURNAL, $id, $date_, $memo); $Refs->save(ST_JOURNAL, $id, $ref); } } + $gl_cart->add_gl_item($clearing_act, $dimension, $dimension2, $taxfree_line); } - add_or_update_purchase_data($supp_trans->supplier_id, $entered_grn->item_code, $entered_grn->chg_price); + else + $gl_cart->add_gl_item($stock_gl_code["inventory_account"], $dimension, $dimension2, $taxfree_line); + } - update_purchase_value($entered_grn->item_code, $entered_grn->chg_price * $ex_rate); - add_supp_invoice_item($trans_type, $invoice_id, $entered_grn->item_code, - $entered_grn->item_description, 0, $entered_grn->chg_price, $line_tax/$entered_grn->this_quantity_inv, - $entered_grn->this_quantity_inv, $entered_grn->id, $entered_grn->po_detail_item, ""); - } /* end of GRN postings */ - /*Post a balance post if $total != 0 */ - add_gl_balance($trans_type, $invoice_id, $date_, -$total, PT_SUPPLIER, $supp_trans->supplier_id); // ?? +// if (is_fa_item($item->item_code)) { +// add_actual_cost($item->order_price, $item->item_code); +// } + + add_or_update_purchase_data($supp_trans->supplier_id, $item->item_code, $item->chg_price); + + add_supp_invoice_item($trans_type, $invoice_id, $item->item_code, + $item->item_description, 0, $item->chg_price, $line_tax/$item->this_quantity_inv, + $item->this_quantity_inv, $item->id, $item->po_detail_item); + } // grn_items + + /*GL Items are straight forward - just do the debit postings to the GL accounts specified - + the credit is to creditors control act */ + foreach ($supp_trans->gl_codes as $entered_gl_code) + { + if ($trans_type == ST_SUPPCREDIT) + $entered_gl_code->amount = -$entered_gl_code->amount; + + $memo_ = $entered_gl_code->memo_; + + $index = is_tax_account($entered_gl_code->gl_code); + if ($index !== false) + { + $gl_cart->add_gl_item($entered_gl_code->gl_code, $entered_gl_code->gl_dim, $entered_gl_code->gl_dim2, $entered_gl_code->amount); + // store tax details if the gl account is a tax account + if ($trans_type == ST_SUPPCREDIT) + $entered_gl_code->amount = -$entered_gl_code->amount; + add_gl_tax_details($entered_gl_code->gl_code, + $trans_type, $invoice_id, $entered_gl_code->amount, + $supp_trans->ex_rate, $date_, $supp_trans->supp_reference, $supp_trans->tax_included, null, $supp_trans->tax_group_id); + + $tax_total += $entered_gl_code->amount; + } else { + $gl_cart->add_gl_item($entered_gl_code->gl_code, $entered_gl_code->gl_dim, $entered_gl_code->gl_dim2, $entered_gl_code->amount); + $net_total += $entered_gl_code->amount; + } + $gl_cart->add_gl_item($supplier["payable_account"], 0, 0, -$entered_gl_code->amount, '', '', $supp_trans->supplier_id); + + add_supp_invoice_gl_item($trans_type, $invoice_id, $entered_gl_code->gl_code, $entered_gl_code->amount, $memo_, + $entered_gl_code->gl_dim, $entered_gl_code->gl_dim2); + } + + $gl_cart->reduce_gl(); // minimize GL lines + + $gl_cart->write_gl(false); // don't check balances here: we are working on two (maybe unbalanced) carts + + update_supp_trans_sums($trans_type, $invoice_id, round($net_total,2), round($tax_total,2)); add_comments($trans_type, $invoice_id, $date_, $supp_trans->Comments); @@ -395,11 +391,10 @@ function add_supp_invoice(&$supp_trans) } } - reallocate_payments($invoice_id, ST_SUPPINVOICE, $date_, $to_allocate, $allocs, $supp_trans->supplier_id); - $supp_trans->trans_no = $invoice_id; + reallocate_payments($invoice_id, ST_SUPPINVOICE, $date_, $net_total+$tax_total, $allocs, $supp_trans->supplier_id); + hook_db_postwrite($supp_trans, $supp_trans->trans_type); commit_transaction(); - return $invoice_id; } diff --git a/purchasing/includes/db/invoice_items_db.inc b/purchasing/includes/db/invoice_items_db.inc index a430de51..f090c6d0 100644 --- a/purchasing/includes/db/invoice_items_db.inc +++ b/purchasing/includes/db/invoice_items_db.inc @@ -12,7 +12,7 @@ //------------------------------------------------------------------------------------------------------------- function add_supp_invoice_item($supp_trans_type, $supp_trans_no, $stock_id, $description, - $gl_code, $unit_price, $unit_tax, $quantity, $grn_item_id, $po_detail_item_id, $memo_, $dim_id=0, $dim2_id=0) + $gl_code, $unit_price, $unit_tax, $quantity, $grn_item_id, $po_detail_item_id, $memo_="", $dim_id=0, $dim2_id=0) { $sql = "INSERT INTO ".TB_PREF."supp_invoice_items (supp_trans_type, supp_trans_no, stock_id, description, gl_code, unit_price, unit_tax, quantity, grn_item_id, po_detail_item_id, memo_, dimension_id, dimension2_id) "; diff --git a/purchasing/includes/db/po_db.inc b/purchasing/includes/db/po_db.inc index a18083ce..eb26570f 100644 --- a/purchasing/includes/db/po_db.inc +++ b/purchasing/includes/db/po_db.inc @@ -15,10 +15,11 @@ function get_supplier_details_to_order(&$order, $supplier_id) { $sql = "SELECT curr_code, supp_name, tax_group_id, supp.tax_included, dimension_id, dimension2_id, supp.credit_limit - Sum((ov_amount + ov_gst + ov_discount)) as cur_credit, - terms.terms, terms.days_before_due, terms.day_in_following_month + terms.terms, terms.days_before_due, terms.day_in_following_month, tg.tax_area FROM ".TB_PREF."suppliers supp LEFT JOIN ".TB_PREF."supp_trans trans ON supp.supplier_id = trans.supplier_id LEFT JOIN ".TB_PREF."payment_terms terms ON supp.payment_terms=terms.terms_indicator + LEFT JOIN ".TB_PREF."tax_groups tg ON supp.tax_group_id=tg.id WHERE supp.supplier_id = ".db_escape($supplier_id)." GROUP BY supp.supp_name"; @@ -39,7 +40,7 @@ function get_supplier_details_to_order(&$order, $supplier_id) $_POST['dimension2'] = $myrow["dimension2_id"]; $order->set_supplier($supplier_id, $myrow["supp_name"], $myrow["curr_code"], - $myrow["tax_group_id"], $myrow["tax_included"]); + $myrow["tax_group_id"], $myrow["tax_included"], $myrow["tax_area"]); } //---------------------------------------------------------------------------------------- @@ -162,9 +163,10 @@ function update_po(&$po_obj) function read_po_header($order_no, &$order) { - $sql = "SELECT po.*, supplier.*, loc.location_name + $sql = "SELECT po.*, tg.tax_area, supplier.*, loc.location_name FROM ".TB_PREF."purch_orders po," - .TB_PREF."suppliers supplier," + .TB_PREF."suppliers supplier + LEFT JOIN ".TB_PREF."tax_groups tg ON supplier.tax_group_id=tg.id," .TB_PREF."locations loc WHERE po.supplier_id = supplier.supplier_id AND loc.loc_code = into_stock_location @@ -181,7 +183,7 @@ function read_po_header($order_no, &$order) $order->order_no = $order_no; $order->set_supplier($myrow["supplier_id"], $myrow["supp_name"], $myrow["curr_code"], - $myrow['tax_group_id'], $myrow["tax_included"]); + $myrow['tax_group_id'], $myrow["tax_included"], $myrow["tax_area"]); $order->credit = get_current_supp_credit($order->supplier_id); diff --git a/purchasing/includes/db/supp_trans_db.inc b/purchasing/includes/db/supp_trans_db.inc index 33a2164f..618ddeeb 100644 --- a/purchasing/includes/db/supp_trans_db.inc +++ b/purchasing/includes/db/supp_trans_db.inc @@ -47,6 +47,19 @@ function write_supp_trans($type, $trans_no, $supplier_id, $date_, $due_date, $re return $trans_no; } +/* + Helper for final supplier trans header summaries update. +*/ +function update_supp_trans_sums($trans_type, $trans_no, $amount, $tax, $discount=0) +{ + $sql = "UPDATE ".TB_PREF."supp_trans + SET ov_amount=".db_escape($amount).", + ov_gst=".db_escape($tax).", + ov_discount=".db_escape($discount)." + WHERE trans_no=".db_escape($trans_no)." AND type=".db_escape($trans_type); + + return db_query($sql, "Cannot update supplier transaction summaries"); +} //------------------------------------------------------------------------------------------------------------- diff --git a/purchasing/includes/po_class.inc b/purchasing/includes/po_class.inc index 22a9bc65..0053b0b1 100644 --- a/purchasing/includes/po_class.inc +++ b/purchasing/includes/po_class.inc @@ -36,7 +36,6 @@ class purch_order var $lines_on_order = 0; var $credit; var $tax_group_id; - var $tax_group_array = null; // saves db queries var $terms; var $ex_rate; var $cash_account; @@ -52,19 +51,19 @@ class purch_order function __construct() { - /*Constructor function initialises a new purchase order object */ $this->line_items = array(); $this->lines_on_order = $this->order_no = $this->supplier_id = 0; + $this->tax_group_id = find_domestic_tax_group(); // prevent tax errors until supplier is selected } - function set_supplier($supplier_id, $supplier_name, $curr_code, $tax_group_id, $tax_included) + function set_supplier($supplier_id, $supplier_name, $curr_code, $tax_group_id, $tax_included, $tax_area) { $this->supplier_id = $supplier_id; $this->supplier_name = $supplier_name; $this->curr_code = $curr_code; $this->tax_group_id = $tax_group_id; $this->tax_included = $tax_included; - $this->tax_group_array = get_tax_group_items_as_array($tax_group_id); + $this->tax_area = $tax_area; } function add_to_order($line_no, $stock_id, $qty, $item_descr, $price, $uom, $req_del_date, $qty_inv, $qty_recd, $qty_ordered=0, $grn_item_id=0) @@ -154,8 +153,8 @@ class purch_order $items[] = $ln_itm->stock_id; $prices[] = round($ln_itm->price * $ln_itm->quantity, user_price_dec()); } - $taxes = get_tax_for_items($items, $prices, $shipping_cost, - $this->tax_group_id, $this->tax_included, $this->tax_group_array); + $taxes = get_tax_for_items($this->trans_type, $items, $prices, $shipping_cost, + $this->tax_group_id, $this->tax_included); // Adjustment for swiss franken, we always have 5 rappen = 1/20 franken if ($this->curr_code == 'CHF') { @@ -186,8 +185,8 @@ class purch_order } if (!$this->tax_included ) { - $taxes = get_tax_for_items($items, $prices, 0, $this->tax_group_id, - $this->tax_included, $this->tax_group_array); + $taxes = get_tax_for_items($this->trans_type, $items, $prices, 0, $this->tax_group_id, + $this->tax_included); foreach($taxes as $tax) $total += round($tax['Value'], $dec); @@ -195,6 +194,40 @@ class purch_order return $total; } + function split_line_values() + { + // split nominal line values + foreach($this->line_items as $line) + $line->split_item_value($this); + + // Exact tax values are currently entered as tax totals, so we need to move the differences back on line level. + // currently first item with given tax type will be fixed with the calculated difference + // FIXME: change UI moving tax edit to line level in line edit mode, then this workaround will be obsolete. + foreach($this->get_taxes() as $tax_id => $tax) + { + if ($tax['Value'] != 0 && ($tax['Value'] != $tax['Override'])) + { + foreach($this->line_items as $id => $line) + if ($line->gl_amounts[0]['tax_type_id'] == $tax_id) // assumed single tax rate on item, so always gl_mount[0] is valid + { + $diff = $tax['Override'] - $tax['Value']; + $this->line_items[$id]->gl_amounts[0]['Value'] += $diff; + if ($this->vat_category() != VC_NONDEDUCT) + $this->line_items[$id]->gl_amounts[0]['Deductible'] += $diff; + else + $this->line_items[$id]->gl_amounts['Cost'] += $diff; + // when supplier uses net prices the price is exact, so don't fix net, still record exact VAT. + if ($this->tax_included) + { + $this->line_items[$id]->gl_amounts['Net'] -= $diff; + $this->line_items[$id]->gl_amounts['Cost'] -= $diff; + } + break; + } + } + } + } + } /* end of class defintion */ class po_line_details @@ -216,6 +249,7 @@ class po_line_details var $unit_cost; var $descr_editable; + var $vat_category; function __construct($line_no, $stock_item, $item_descr, $qty, $prc, $uom, $req_del_date, $qty_inv, $qty_recd, $qty_ordered=0, $grn_item_id=0) @@ -241,6 +275,7 @@ class po_line_details $this->qty_inv = $qty_inv; $this->unit_cost =0; $this->grn_item_id = $grn_item_id; + $this->vat_category = $item_row["vat_category"]; $this->qty_ordered = $qty_ordered; } @@ -249,8 +284,20 @@ class po_line_details // function taxfree_charge_value($po) { - return get_tax_free_price_for_item($this->stock_id, $this->quantity*$this->price, - $po->tax_group_id, $po->tax_included, $po->tax_group_array); + return get_tax_free_price_for_item($po->trans_type, $this->stock_id, $this->quantity*$this->price, + $po->tax_group_id, $po->tax_included); + } + + /* + Splits item value to parts posted to GL. + */ + function split_item_value($cart) + { + $vat_factor = 1; + + return $this->gl_amounts = split_item_price($this->stock_id, $this->price*$this->quantity, $cart->tax_group_id, $cart->tax_included, + ST_SUPPINVOICE, $vat_factor, $cart->tran_date); } + } diff --git a/purchasing/includes/supp_trans_class.inc b/purchasing/includes/supp_trans_class.inc index be9ec00d..107890df 100644 --- a/purchasing/includes/supp_trans_class.inc +++ b/purchasing/includes/supp_trans_class.inc @@ -41,7 +41,6 @@ class supp_trans var $ov_amount; var $ov_discount; var $ov_gst; - var $gl_codes_counter=0; var $credit = 0; var $currency; var $tax_overrides = array(); // array of taxes manually inserted during sales invoice entry @@ -88,9 +87,7 @@ class supp_trans function add_gl_codes_to_trans($gl_code, $gl_act_name, $gl_dim, $gl_dim2, $amount, $memo_) { - $this->gl_codes[$this->gl_codes_counter] = new gl_codes($this->gl_codes_counter, - $gl_code, $gl_act_name, $gl_dim, $gl_dim2, $amount, $memo_); - $this->gl_codes_counter++; + $this->gl_codes[] = new gl_codes($gl_code, $gl_act_name, $gl_dim, $gl_dim2, $amount, $memo_); unset($this->tax_overrides); // cancel tax overrides after cart change return 1; } @@ -142,7 +139,7 @@ class supp_trans if ($tax_group_id == null) $tax_group_id = $this->tax_group_id; - $taxes = get_tax_for_items($items, $prices, $shipping_cost, $tax_group_id, + $taxes = get_tax_for_items($this->trans_type, $items, $prices, $shipping_cost, $tax_group_id, $this->tax_included); if (isset($this->tax_overrides)) @@ -209,6 +206,39 @@ class supp_trans } return $total; } + + function split_line_values() + { + // split nominal line values + foreach($this->grn_items as $line) + $line->split_item_value($this); + + // Exact tax values are currently entered as tax totals, so we need to move the differences back on line level. + // currently first item with given tax type will be fixed with the calculated difference + // FIXME: change UI moving tax edit to line level in line edit mode, then this workaround will be obsolete. + foreach($this->get_taxes() as $tax_id => $tax) + { + if ($tax['Value'] != 0 && isset($tax['Override']) && ($tax['Value'] != $tax['Override'])) + { + foreach($this->grn_items as $id => $line) + if ($line->gl_amounts[0]['tax_type_id'] == $tax_id) // assumed single tax rate on item, so always gl_mount[0] is valid + { + $diff = $tax['Override'] - $tax['Value']; + $this->grn_items[$id]->gl_amounts[0]['Value'] += $diff; + if ($this->vat_category() != VC_NONDEDUCT) + $this->grn_items[$id]->gl_amounts[0]['Deductible'] += $diff; + else + $this->grn_items[$id]->gl_amounts['Cost'] += $diff; + // when supplier uses net prices the price is exact, so don't fix net, still record exact VAT. + if ($this->tax_included) { + $this->grn_items[$id]->gl_amounts['Net'] -= $diff; + $this->grn_items[$id]->gl_amounts['Cost'] -= $diff; + } + break; + } + } + } + } } /* end of class defintion */ class grn_item @@ -229,6 +259,8 @@ all the info to do the necessary entries without looking up ie additional querie var $std_cost_unit; var $gl_code; var $tax_included; + var $gl_amounts; // splited line value (after call to split_line_value method + var $vat_category; function __construct($id, $po_detail_item, $item_code, $item_description, $qty_recd, $prev_quantity_inv, $this_quantity_inv, $order_price, $chg_price, @@ -247,26 +279,46 @@ all the info to do the necessary entries without looking up ie additional querie $this->std_cost_unit = $std_cost_unit; $this->gl_code = $gl_code; $this->tax_included = $tax_included; + + $opts = get_item($item_code); + $this->vat_category = $opts['vat_category']; } - function full_charge_price($tax_group_id, $tax_group=null) + function full_charge_price($tax_group_id, $trans_type=ST_PURCHORDER) { - return get_full_price_for_item($this->item_code, - $this->chg_price, $tax_group_id, $this->tax_included, $tax_group); + return get_full_price_for_item($trans_type, $this->item_code, + $this->chg_price, $tax_group_id, $this->tax_included); } - function taxfree_charge_price($tax_group_id, $tax_group=null) + function taxfree_charge_price($tax_group_id, $trans_type=ST_PURCHORDER) { - return get_tax_free_price_for_item($this->item_code, $this->chg_price, + return get_tax_free_price_for_item($trans_type, $this->item_code, $this->chg_price, + $tax_group_id, $this->tax_included); + } + + function taxfree_value($tax_group_id, $trans_type=ST_PURCHORDER) + { + return get_tax_free_price_for_item($trans_type, $this->item_code, $this->this_quantity_inv * $this->chg_price, $tax_group_id, $this->tax_included, $tax_group); } + + /* + Splits item value to parts posted to GL. + */ + function split_item_value($cart) + { + + $vat_factor = 1; + + return $this->gl_amounts = split_item_price($this->item_code, $this->chg_price*$this->this_quantity_inv, $cart->tax_group_id, $cart->tax_included, + ST_SUPPINVOICE, $vat_factor, $cart->tran_date); + } } class gl_codes { - var $Counter; var $gl_code; var $gl_act_name; var $gl_dim; @@ -274,11 +326,8 @@ class gl_codes var $amount; var $memo_; - function __construct($Counter, $gl_code, $gl_act_name, $gl_dim, $gl_dim2, $amount, $memo_) + function __construct($gl_code, $gl_act_name, $gl_dim, $gl_dim2, $amount, $memo_) { - - /* Constructor function to add a new gl_codes object with passed params */ - $this->Counter = $Counter; $this->gl_code = $gl_code; $this->gl_act_name = $gl_act_name; $this->gl_dim = $gl_dim; diff --git a/purchasing/includes/ui/invoice_ui.inc b/purchasing/includes/ui/invoice_ui.inc index 39a4d5ba..7dafc8a9 100644 --- a/purchasing/includes/ui/invoice_ui.inc +++ b/purchasing/includes/ui/invoice_ui.inc @@ -292,7 +292,7 @@ function display_gl_items(&$supp_trans, $mode=0) if (count($supp_trans->gl_codes) > 0) { - foreach ($supp_trans->gl_codes as $entered_gl_code) + foreach ($supp_trans->gl_codes as $n => $entered_gl_code) { alt_table_row_color($k); @@ -313,9 +313,9 @@ function display_gl_items(&$supp_trans, $mode=0) if ($mode == 1) { - delete_button_cell("Delete2" . $entered_gl_code->Counter, _("Delete"), + delete_button_cell("Delete2" . $n, _("Delete"), _('Remove line from document')); - edit_button_cell("Edit" . $entered_gl_code->Counter, _("Edit"), + edit_button_cell("Edit" . $n, _("Edit"), _('Edit line from document')); } end_row(); diff --git a/purchasing/view/view_grn.php b/purchasing/view/view_grn.php index b5159b63..0efdc273 100644 --- a/purchasing/view/view_grn.php +++ b/purchasing/view/view_grn.php @@ -51,7 +51,7 @@ foreach ($purchase_order->line_items as $stock_item) $line_total = $stock_item->quantity * $stock_item->price; // if overdue and outstanding quantities, then highlight as so - if (date1_greater_date2($purchase_order->orig_order_date, $stock_item->req_del_date)) + if (date1_greater_date2($purchase_order->tran_date, $stock_item->req_del_date)) { start_row("class='overduebg'"); $overdue_items = true; diff --git a/reporting/rep109.php b/reporting/rep109.php index ba6c86ce..e96af1d4 100644 --- a/reporting/rep109.php +++ b/reporting/rep109.php @@ -168,7 +168,7 @@ function print_sales_orders() $rep->NewLine(); } - $tax_items = get_tax_for_items($items, $prices, $myrow["freight_cost"], + $tax_items = get_tax_for_items(ST_SALESINVOICE, $items, $prices, $myrow["freight_cost"], $myrow['tax_group_id'], $myrow['tax_included'], null); $first = true; foreach($tax_items as $tax_item) diff --git a/reporting/rep111.php b/reporting/rep111.php index 1db74b7c..23ccc7f1 100644 --- a/reporting/rep111.php +++ b/reporting/rep111.php @@ -165,7 +165,7 @@ function print_sales_quotations() $rep->NewLine(); } - $tax_items = get_tax_for_items($items, $prices, $myrow["freight_cost"], + $tax_items = get_tax_for_items(ST_SALESORDER, $items, $prices, $myrow["freight_cost"], $myrow['tax_group_id'], $myrow['tax_included'], null); $first = true; foreach($tax_items as $tax_item) diff --git a/reporting/rep209.php b/reporting/rep209.php index f6fabf31..8b4decbd 100644 --- a/reporting/rep209.php +++ b/reporting/rep209.php @@ -167,7 +167,7 @@ function print_po() $rep->TextCol(6, 7, $DisplaySubTot, -2); $rep->NewLine(); - $tax_items = get_tax_for_items($items, $prices, 0, + $tax_items = get_tax_for_items(ST_PURCHORDER, $items, $prices, 0, $myrow['tax_group_id'], $myrow['tax_included'], null, TCA_LINES); $first = true; foreach($tax_items as $tax_item) diff --git a/sales/includes/cart_class.inc b/sales/includes/cart_class.inc index c6b6f353..6a7b3853 100644 --- a/sales/includes/cart_class.inc +++ b/sales/includes/cart_class.inc @@ -517,7 +517,7 @@ class Cart $ln_itm->line_price()* (1 - $ln_itm->discount_percent)), user_price_dec()); } - $taxes = get_tax_for_items($items, $prices, $shipping_cost, + $taxes = get_tax_for_items($this->trans_type, $items, $prices, $shipping_cost, $this->tax_group_id, $this->tax_included, $this->tax_group_array); // Adjustment for swiss franken, we always have 5 rappen = 1/20 franken diff --git a/sales/includes/db/sales_credit_db.inc b/sales/includes/db/sales_credit_db.inc index 999624f0..6c3a8f28 100644 --- a/sales/includes/db/sales_credit_db.inc +++ b/sales/includes/db/sales_credit_db.inc @@ -108,10 +108,10 @@ function write_credit_note(&$credit_note, $write_off_acc) -$credit_line->qty_old)); } - $line_taxfree_price = get_tax_free_price_for_item($credit_line->stock_id, $credit_line->price, + $line_taxfree_price = get_tax_free_price_for_item(ST_CUSTCREDIT, $credit_line->stock_id, $credit_line->price, 0, $credit_note->tax_included, $credit_note->tax_group_array); - $line_tax = get_full_price_for_item($credit_line->stock_id, $credit_line->price, + $line_tax = get_full_price_for_item(ST_CUSTCREDIT, $credit_line->stock_id, $credit_line->price, 0, $credit_note->tax_included, $credit_note->tax_group_array) - $line_taxfree_price; $credit_line->unit_cost = get_unit_cost($credit_line->stock_id); @@ -153,7 +153,7 @@ function write_credit_note(&$credit_note, $write_off_acc) add_trans_tax_details(ST_CUSTCREDIT, $credit_no, $taxitem['tax_type_id'], $taxitem['rate'], $credit_note->tax_included, $taxitem['Value'], $taxitem['Net'], $ex_rate, - $credit_note->document_date, $credit_note->reference, TR_OUTPUT); + $credit_note->document_date, $credit_note->reference, TR_OUTPUT, $credit_note->tax_group_id); $total += add_gl_trans_customer(ST_CUSTCREDIT, $credit_no, $credit_date, $taxitem['sales_gl_code'], 0, 0, $taxitem['Value'], $credit_note->customer_id, @@ -237,10 +237,10 @@ function add_gl_trans_credit_costs($order, $order_line, $credit_no, $date_, if ($order_line->line_price() != 0) { $line_taxfree_price = - get_tax_free_price_for_item($order_line->stock_id, $order_line->price, + get_tax_free_price_for_item(ST_CUSTCREDIT, $order_line->stock_id, $order_line->price, 0, $order->tax_included, $order->tax_group_array); - $line_tax = get_full_price_for_item($order_line->stock_id, $order_line->price, + $line_tax = get_full_price_for_item(ST_CUSTCREDIT, $order_line->stock_id, $order_line->price, 0, $order->tax_included, $order->tax_group_array) - $line_taxfree_price; diff --git a/sales/includes/db/sales_delivery_db.inc b/sales/includes/db/sales_delivery_db.inc index e5ef6756..f1ae7a8a 100644 --- a/sales/includes/db/sales_delivery_db.inc +++ b/sales/includes/db/sales_delivery_db.inc @@ -72,11 +72,11 @@ function write_sales_delivery(&$delivery,$bo_policy) $qty = $delivery_line->qty_dispatched; $line_price = $delivery_line->line_price(); - $line_taxfree_price = get_tax_free_price_for_item($delivery_line->stock_id, - $delivery_line->price*$qty, 0, $delivery->tax_included, + $line_taxfree_price = get_tax_free_price_for_item(ST_CUSTDELIVERY, $delivery_line->stock_id, + $delivery_line->price*$qty, $delivery->tax_group_id, $delivery->tax_included, $delivery->tax_group_array); - $line_tax = get_full_price_for_item($delivery_line->stock_id, + $line_tax = get_full_price_for_item(ST_CUSTDELIVERY, $delivery_line->stock_id, $delivery_line->price * $qty, 0, $delivery->tax_included, $delivery->tax_group_array) - $line_taxfree_price; $delivery_line->unit_cost = get_unit_cost($delivery_line->stock_id); @@ -188,7 +188,7 @@ function write_sales_delivery(&$delivery,$bo_policy) $ex_rate = get_exchange_rate_from_home_currency(get_customer_currency($delivery->customer_id), $delivery->document_date); add_trans_tax_details(ST_CUSTDELIVERY, $delivery_no, $taxitem['tax_type_id'], $taxitem['rate'], $delivery->tax_included, $taxitem['Value'], - $taxitem['Net'], $ex_rate, $delivery->document_date, $delivery->reference, null); + $taxitem['Net'], $ex_rate, $delivery->document_date, $delivery->reference, null, $delivery->tax_group_id); } } diff --git a/sales/includes/db/sales_invoice_db.inc b/sales/includes/db/sales_invoice_db.inc index 901cdd08..376a6c84 100644 --- a/sales/includes/db/sales_invoice_db.inc +++ b/sales/includes/db/sales_invoice_db.inc @@ -104,11 +104,11 @@ function write_sales_invoice(&$invoice) foreach ($invoice->line_items as $line_no => $invoice_line) { $qty = $invoice_line->qty_dispatched; - $line_taxfree_price = get_tax_free_price_for_item($invoice_line->stock_id, - $invoice_line->price * $qty, 0, $invoice->tax_included, + $line_taxfree_price = get_tax_free_price_for_item(ST_SALESINVOICE, $invoice_line->stock_id, + $invoice_line->price * $qty, $invoice->tax_group_id, $invoice->tax_included, $invoice->tax_group_array); - $line_tax = get_full_price_for_item($invoice_line->stock_id, + $line_tax = get_full_price_for_item(ST_SALESINVOICE, $invoice_line->stock_id, $invoice_line->price * $qty, 0, $invoice->tax_included, $invoice->tax_group_array) - $line_taxfree_price; @@ -175,7 +175,7 @@ function write_sales_invoice(&$invoice) $ex_rate = get_exchange_rate_from_home_currency(get_customer_currency($invoice->customer_id), $date_); add_trans_tax_details(ST_SALESINVOICE, $invoice_no, $taxitem['tax_type_id'], $taxitem['rate'], $invoice->tax_included, $prepaid_factor*$taxitem['Value'], - $taxitem['Net'], $ex_rate, $date_, $invoice->reference, TR_OUTPUT); + $taxitem['Net'], $ex_rate, $date_, $invoice->reference, TR_OUTPUT, $invoice->tax_group_id); if (isset($taxitem['sales_gl_code']) && !empty($taxitem['sales_gl_code']) && $taxitem['Value'] != 0) $total += add_gl_trans_customer(ST_SALESINVOICE, $invoice_no, $date_, $taxitem['sales_gl_code'], 0, 0, (-$taxitem['Value'])*$prepaid_factor, $invoice->customer_id, @@ -271,7 +271,6 @@ function void_sales_invoice($type, $type_no) // do this last because other voidings can depend on it - especially voiding // DO NOT MOVE THIS ABOVE VOIDING or we can end up with trans with alloc < 0 void_customer_trans($type, $type_no); - commit_transaction(); } diff --git a/sales/includes/db/sales_order_db.inc b/sales/includes/db/sales_order_db.inc index c918890b..9a8272a0 100644 --- a/sales/includes/db/sales_order_db.inc +++ b/sales/includes/db/sales_order_db.inc @@ -642,3 +642,14 @@ function last_sales_order_detail($order, $field) return $row[0]; } +function get_sales_vat_category($trans_no, $type=ST_SALESINVOICE) +{ + $sql = "SELECT vat_category + FROM ".TB_PREF."debtor_trans_details line + LEFT JOIN ".TB_PREF."stock_master stock ON line.stock_id=stock.stock_id + WHERE debtor_trans_type=".db_escape($type)." AND debtor_trans_no=".db_escape($trans_no); + $result = db_query($sql, 'cannot check invoice category'); + $line = db_fetch($result); + return $line['vat_category']; +} + diff --git a/sql/alter2.5.sql b/sql/alter2.5.sql index 215b18ed..c92046e0 100644 --- a/sql/alter2.5.sql +++ b/sql/alter2.5.sql @@ -8,4 +8,19 @@ ALTER TABLE `0_purch_orders` CHANGE COLUMN `requisition_no` `supp_reference` tin # cleanups in work orders ALTER TABLE `0_workorders` DROP INDEX `wo_ref`; ALTER TABLE `0_workorders` ADD KEY `wo_ref` (`wo_ref`); -ALTER TABLE `0_workorders` DROP COLUMN `additional_costs`; \ No newline at end of file +ALTER TABLE `0_workorders` DROP COLUMN `additional_costs`; + +# improvements in tax systems support +ALTER TABLE `0_stock_category` ADD COLUMN `vat_category` tinyint(1) NOT NULL DEFAULT '0' AFTER `dflt_no_purchase`; +ALTER TABLE `0_stock_master` ADD COLUMN `vat_category` tinyint(1) NOT NULL DEFAULT '0' AFTER `fa_class_id`; +ALTER TABLE `0_trans_tax_details` ADD COLUMN `vat_category` tinyint(1) NOT NULL DEFAULT '0' AFTER `reg_type`; +ALTER TABLE `0_trans_tax_details` ADD COLUMN `tax_group_id` int(11) DEFAULT NULL AFTER `vat_category`; + +UPDATE `0_trans_tax_details` tax + LEFT JOIN `0_supp_trans` purch ON tax.trans_no=purch.trans_no AND tax.trans_type=purch.type + LEFT JOIN `0_suppliers` supp ON purch.supplier_id=supp.supplier_id + LEFT JOIN `0_debtor_trans` sales ON tax.trans_no=sales.trans_no AND tax.trans_type=sales.type + LEFT JOIN `0_cust_branch` cust ON sales.branch_code=cust.branch_code + SET tax.tax_group_id = IFNULL(supp.tax_group_id, cust.tax_group_id); + +ALTER TABLE `0_tax_groups` ADD COLUMN `tax_area` tinyint(1) NOT NULL DEFAULT '0' AFTER `name`; diff --git a/sql/en_US-demo.sql b/sql/en_US-demo.sql index 20d7d2e7..1d7184cb 100644 --- a/sql/en_US-demo.sql +++ b/sql/en_US-demo.sql @@ -1615,6 +1615,7 @@ CREATE TABLE `0_stock_category` ( `inactive` tinyint(1) NOT NULL DEFAULT '0', `dflt_no_sale` tinyint(1) NOT NULL DEFAULT '0', `dflt_no_purchase` tinyint(1) NOT NULL DEFAULT '0', + `vat_category` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`category_id`), UNIQUE KEY `description` (`description`) ) ENGINE=InnoDB AUTO_INCREMENT=5 ; @@ -1622,10 +1623,10 @@ CREATE TABLE `0_stock_category` ( -- Data of table `0_stock_category` -- INSERT INTO `0_stock_category` VALUES -('1', 'Components', '1', 'each', 'B', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '0', '0'), -('2', 'Charges', '1', 'each', 'D', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '0', '0'), -('3', 'Systems', '1', 'each', 'M', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '0', '0'), -('4', 'Services', '1', 'hr', 'D', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '0', '0'); +('1', 'Components', '1', 'each', 'B', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '0', '0', '0'), +('2', 'Charges', '1', 'each', 'D', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '0', '0', '0'), +('3', 'Systems', '1', 'each', 'M', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '0', '0', '0'), +('4', 'Services', '1', 'hr', 'D', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '0', '0', '0'); -- Structure of table `0_stock_fa_class` -- @@ -1676,18 +1677,19 @@ CREATE TABLE `0_stock_master` ( `depreciation_start` date NOT NULL DEFAULT '0000-00-00', `depreciation_date` date NOT NULL DEFAULT '0000-00-00', `fa_class_id` varchar(20) NOT NULL DEFAULT '', + `vat_category` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`stock_id`) ) ENGINE=InnoDB ; -- Data of table `0_stock_master` -- INSERT INTO `0_stock_master` VALUES -('101', '1', '1', 'iPad Air 2 16GB', '', 'each', 'B', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '200', '0', '0', '0', '0', '0', '0', 'S', '0', '1', '0000-00-00', '0000-00-00', ''), -('102', '1', '1', 'iPhone 6 64GB', '', 'each', 'B', '4010', '5010', '1510', '5040', '1530', '0', '0', '150', '150', '0', '0', '0', '0', '0', '0', 'S', '0', '1', '0000-00-00', '0000-00-00', ''), -('103', '1', '1', 'iPhone Cover Case', '', 'each', 'B', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '10', '0', '0', '0', '0', '0', '0', 'S', '0', '1', '0000-00-00', '0000-00-00', ''), -('201', '3', '1', 'AP Surf Set', '', 'each', 'M', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '360', '0', '0', '0', '0', '0', '0', 'S', '0', '1', '0000-00-00', '0000-00-00', ''), -('202', '4', '1', 'Maintenance', '', 'hr', 'D', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1', 'S', '0', '1', '0000-00-00', '0000-00-00', ''), -('301', '4', '1', 'Support', '', 'hr', 'D', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', 'S', '0', '1', '0000-00-00', '0000-00-00', ''); +('101', '1', '1', 'iPad Air 2 16GB', '', 'each', 'B', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '200', '0', '0', '0', '0', '0', '0', 'S', '0', '1', '0000-00-00', '0000-00-00', '', '0'), +('102', '1', '1', 'iPhone 6 64GB', '', 'each', 'B', '4010', '5010', '1510', '5040', '1530', '0', '0', '150', '150', '0', '0', '0', '0', '0', '0', 'S', '0', '1', '0000-00-00', '0000-00-00', '', '0'), +('103', '1', '1', 'iPhone Cover Case', '', 'each', 'B', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '10', '0', '0', '0', '0', '0', '0', 'S', '0', '1', '0000-00-00', '0000-00-00', '', '0'), +('201', '3', '1', 'AP Surf Set', '', 'each', 'M', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '360', '0', '0', '0', '0', '0', '0', 'S', '0', '1', '0000-00-00', '0000-00-00', '', '0'), +('202', '4', '1', 'Maintenance', '', 'hr', 'D', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1', 'S', '0', '1', '0000-00-00', '0000-00-00', '', '0'), +('301', '4', '1', 'Support', '', 'hr', 'D', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', 'S', '0', '1', '0000-00-00', '0000-00-00', '', '0'); -- Structure of table `0_stock_moves` -- @@ -1990,6 +1992,7 @@ DROP TABLE IF EXISTS `0_tax_groups`; CREATE TABLE `0_tax_groups` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(60) NOT NULL DEFAULT '', + `tax_area` tinyint(1) NOT NULL DEFAULT '0', `inactive` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`) @@ -1998,8 +2001,8 @@ CREATE TABLE `0_tax_groups` ( -- Data of table `0_tax_groups` -- INSERT INTO `0_tax_groups` VALUES -('1', 'Tax', '0'), -('2', 'Tax Exempt', '0'); +('1', 'Tax', '0', '0'), +('2', 'Export/Import', '1', '0'); -- Structure of table `0_tax_types` -- @@ -2037,6 +2040,8 @@ CREATE TABLE `0_trans_tax_details` ( `amount` double NOT NULL DEFAULT '0', `memo` tinytext, `reg_type` tinyint(1) DEFAULT NULL, + `vat_category` int(11) NOT NULL DEFAULT '0', + `tax_group_id` tinyint(2) DEFAULT NULL, PRIMARY KEY (`id`), KEY `Type_and_Number` (`trans_type`,`trans_no`), KEY `tran_date` (`tran_date`) @@ -2045,16 +2050,16 @@ CREATE TABLE `0_trans_tax_details` ( -- Data of table `0_trans_tax_details` -- INSERT INTO `0_trans_tax_details` VALUES -('1', '13', '1', '2018-05-10', '1', '5', '1', '1', '5942.86', '297.14', 'auto', NULL), -('2', '10', '1', '2018-05-10', '1', '5', '1', '1', '5942.86', '297.14', '001/2018', '0'), -('3', '20', '1', '2018-05-05', '1', '5', '1', '0', '3000', '150', 'rr4', '1'), -('4', '13', '2', '2018-05-07', '1', '5', '1', '1', '285.71', '14.29', 'auto', NULL), -('5', '10', '2', '2018-05-07', '1', '5', '1', '1', '285.71', '14.29', '002/2018', '0'), -('6', '13', '3', '2018-05-07', '0', '0', '1.123', '1', '267.14', '0', 'auto', NULL), -('7', '10', '3', '2018-05-07', '0', '0', '1.123', '1', '267.14', '0', '003/2018', '0'), -('8', '13', '5', '2019-01-21', '1', '5', '1', '1', '1190.48', '59.52', 'auto', NULL), -('9', '10', '5', '2019-01-21', '1', '5', '1', '1', '1190.48', '59.52', '001/2019', '0'), -('10', '20', '2', '2019-01-21', '1', '5', '1', '0', '900', '45', 'asd5', '1'); +('1', '13', '1', '2018-05-10', '1', '5', '1', '1', '5942.86', '297.14', 'auto', NULL, '0', '1'), +('2', '10', '1', '2018-05-10', '1', '5', '1', '1', '5942.86', '297.14', '001/2018', '0', '0', '1'), +('3', '20', '1', '2018-05-05', '1', '5', '1', '0', '3000', '150', 'rr4', '1', '0', '1'), +('4', '13', '2', '2018-05-07', '1', '5', '1', '1', '285.71', '14.29', 'auto', NULL, '0', '1'), +('5', '10', '2', '2018-05-07', '1', '5', '1', '1', '285.71', '14.29', '002/2018', '0', '0', '1'), +('6', '13', '3', '2018-05-07', '0', '0', '1.123', '1', '267.14', '0', 'auto', NULL, '0', '1'), +('7', '10', '3', '2018-05-07', '0', '0', '1.123', '1', '267.14', '0', '003/2018', '0', '0', '1'), +('8', '13', '5', '2019-01-21', '1', '5', '1', '1', '1190.48', '59.52', 'auto', NULL, '0', '1'), +('9', '10', '5', '2019-01-21', '1', '5', '1', '1', '1190.48', '59.52', '001/2019', '0', '0', '1'), +('10', '20', '2', '2019-01-21', '1', '5', '1', '0', '900', '45', 'asd5', '1', '0', '1'); -- Structure of table `0_useronline` -- diff --git a/sql/en_US-new.sql b/sql/en_US-new.sql index 3dbb1de7..6e2fba32 100644 --- a/sql/en_US-new.sql +++ b/sql/en_US-new.sql @@ -1327,6 +1327,7 @@ CREATE TABLE `0_stock_category` ( `inactive` tinyint(1) NOT NULL DEFAULT '0', `dflt_no_sale` tinyint(1) NOT NULL DEFAULT '0', `dflt_no_purchase` tinyint(1) NOT NULL DEFAULT '0', + `vat_category` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`category_id`), UNIQUE KEY `description` (`description`) ) ENGINE=InnoDB AUTO_INCREMENT=5 ; @@ -1334,10 +1335,10 @@ CREATE TABLE `0_stock_category` ( -- Data of table `0_stock_category` -- INSERT INTO `0_stock_category` VALUES -('1', 'Components', '1', 'each', 'B', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '0', '0'), -('2', 'Charges', '1', 'each', 'D', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '0', '0'), -('3', 'Systems', '1', 'each', 'M', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '0', '0'), -('4', 'Services', '1', 'hr', 'D', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '0', '0'); +('1', 'Components', '1', 'each', 'B', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '0', '0', '0'), +('2', 'Charges', '1', 'each', 'D', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '0', '0', '0'), +('3', 'Systems', '1', 'each', 'M', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '0', '0', '0'), +('4', 'Services', '1', 'hr', 'D', '4010', '5010', '1510', '5040', '1530', '0', '0', '0', '0', '0', '0'); -- Structure of table `0_stock_fa_class` -- @@ -1388,6 +1389,7 @@ CREATE TABLE `0_stock_master` ( `depreciation_start` date NOT NULL DEFAULT '0000-00-00', `depreciation_date` date NOT NULL DEFAULT '0000-00-00', `fa_class_id` varchar(20) NOT NULL DEFAULT '', + `vat_category` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`stock_id`) ) ENGINE=InnoDB; @@ -1663,6 +1665,7 @@ DROP TABLE IF EXISTS `0_tax_groups`; CREATE TABLE `0_tax_groups` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(60) NOT NULL DEFAULT '', + `tax_area` tinyint(1) NOT NULL DEFAULT '0', `inactive` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`) @@ -1671,8 +1674,8 @@ CREATE TABLE `0_tax_groups` ( -- Data of table `0_tax_groups` -- INSERT INTO `0_tax_groups` VALUES -('1', 'Tax', '0'), -('2', 'Tax Exempt', '0'); +('1', 'Tax', '0', '0'), +('2', 'Export/Import', '1', '0'); -- Structure of table `0_tax_types` -- @@ -1710,6 +1713,8 @@ CREATE TABLE `0_trans_tax_details` ( `amount` double NOT NULL DEFAULT '0', `memo` tinytext, `reg_type` tinyint(1) DEFAULT NULL, + `vat_category` int(11) NOT NULL DEFAULT '0', + `tax_group_id` tinyint(2) DEFAULT NULL, PRIMARY KEY (`id`), KEY `Type_and_Number` (`trans_type`,`trans_no`), KEY `tran_date` (`tran_date`) diff --git a/taxes/db/item_tax_types_db.inc b/taxes/db/item_tax_types_db.inc index 446a5dc7..8b37ddf4 100644 --- a/taxes/db/item_tax_types_db.inc +++ b/taxes/db/item_tax_types_db.inc @@ -62,15 +62,16 @@ function get_item_tax_type($id) function get_item_tax_type_for_item($stock_id) { - $sql = "SELECT item_tax_type.* - FROM ".TB_PREF."item_tax_types item_tax_type," + $sql = "SELECT tax_type.*, item.vat_category + FROM ".TB_PREF."item_tax_types tax_type," .TB_PREF."stock_master item - WHERE item.stock_id=".db_escape($stock_id)." - AND item_tax_type.id=item.tax_type_id"; - + WHERE + item.stock_id=".db_escape($stock_id)." + AND tax_type.id=item.tax_type_id"; + $result = db_query($sql, "could not get item tax type"); - - return db_fetch($result); + + return db_fetch($result); } function delete_item_tax_type($id) diff --git a/taxes/db/tax_groups_db.inc b/taxes/db/tax_groups_db.inc index fb40bf7d..dc269fdc 100644 --- a/taxes/db/tax_groups_db.inc +++ b/taxes/db/tax_groups_db.inc @@ -9,25 +9,25 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License here . ***********************************************************************/ -function add_tax_group($name, $taxes, $tax_shippings) +function add_tax_group($name, $taxes, $tax_shippings, $tax_area) { begin_transaction(); - $sql = "INSERT INTO ".TB_PREF."tax_groups (name) VALUES (".db_escape($name).")"; + $sql = "INSERT INTO ".TB_PREF."tax_groups (name, tax_area) VALUES (".db_escape($name).",".db_escape($tax_area).")"; db_query($sql, "could not add tax group"); $id = db_insert_id(); - add_tax_group_items($id, $taxes, $tax_shippings); + add_tax_group_items($id, $taxes, $tax_shippings); commit_transaction(); } -function update_tax_group($id, $name, $taxes, $tax_shippings) +function update_tax_group($id, $name, $taxes, $tax_shippings, $tax_area) { begin_transaction(); - $sql = "UPDATE ".TB_PREF."tax_groups SET name=".db_escape($name)." WHERE id=".db_escape($id); + $sql = "UPDATE ".TB_PREF."tax_groups SET name=".db_escape($name).",tax_area=".db_escape($tax_area)." WHERE id=".db_escape($id); db_query($sql, "could not update tax group"); delete_tax_group_items($id); @@ -49,7 +49,7 @@ function get_tax_group($type_id) $sql = "SELECT * FROM ".TB_PREF."tax_groups WHERE id=".db_escape($type_id); $result = db_query($sql, "could not get tax group"); - + return db_fetch($result); } @@ -125,6 +125,33 @@ function get_tax_group_items_as_array($id) return $ret_tax_array; } +function get_tax_group_data($id) +{ + $data = get_tax_group($id); + + $data['taxes'] = get_tax_group_items_as_array($id); + + return $data; +} + +/* + Heuristic function to find default domestic tax_group. +*/ +function find_domestic_tax_group() +{ + + $sql = "SELECT id, cnt FROM + (SELECT g.id, count(*) cnt + FROM ".TB_PREF."tax_group_items i + LEFT JOIN ".TB_PREF."tax_groups g ON g.id=i.tax_group_id + WHERE tax_shipping=0 AND tax_area=0 AND !inactive + GROUP BY g.id) cnts + ORDER by cnt DESC"; + $result = db_query($sql, "cannot get domestic group id"); + $group = db_fetch($result); + return $group['id']; +} + function get_shipping_tax_as_array($id=null) { $ret_tax_array = array(); diff --git a/taxes/tax_calc.inc b/taxes/tax_calc.inc index 3ef685ae..9f6664ce 100644 --- a/taxes/tax_calc.inc +++ b/taxes/tax_calc.inc @@ -14,227 +14,288 @@ include_once($path_to_root . "/taxes/db/tax_types_db.inc"); include_once($path_to_root . "/taxes/db/item_tax_types_db.inc"); //--------------------------------------------------------------------------------- +// Returns basic fiscal parameters for transaction item which depend on stock type and customer/supplier tax area. +// +// vat_category => stock tax category +// tax_area => cust/supp tax area +// taxes => all taxes applicable: +// tax_type_id1 => (tax_type_id, tax_type_name, sales_gl_code, purchasing_gl_code, rate) +// tax_type_id2 => ... -// returns the price of a given item minus any included taxes -// for item $stock_id with line price $price, -// with applicable tax rates $tax_group_array or group id $tax_group - -function get_tax_free_price_for_item($stock_id, $price, $tax_group, $tax_included, $tax_group_array=null) +function get_base_taxdata($stock_id, $group_id) { - // if price is zero, then can't be taxed ! - if ($price == 0) - return 0; - - if ($tax_included==0) return $price; - - // if array already read, then make a copy and use that - if ($tax_group_array) - $ret_tax_array = $tax_group_array; - else - $ret_tax_array = get_tax_group_items_as_array($tax_group); - - $tax_array = get_taxes_for_item($stock_id, $ret_tax_array); - - // if no exemptions or taxgroup is empty, then no included/excluded taxes - if ($tax_array == null) - return $price; - - // to avoid rounding errors we have to just subtract taxes from tax_included price. - $tax_multiplier = 0; - foreach ($tax_array as $taxitem) - { - $tax_multiplier += $taxitem["rate"]; - } + static $last_group = null, $group_data; - $tax = 0; - foreach ($tax_array as $taxitem) - { - $tax += round($price*$taxitem['rate']/(100+$tax_multiplier), user_price_dec()); + if ($last_group != $group_id) { // cache group data for better performance + $last_group = $group_id; + $group_data = get_tax_group_data($group_id); } - return $price-$tax; -} -// -// Full price (incl. VAT) for item $stock_id with line price $price, -// with tax rates $tax_group_array or applicable group $tax_group -// -function get_full_price_for_item($stock_id, $price, $tax_group, $tax_included, $tax_group_array=null) -{ - // if price is zero, then can't be taxed ! - if ($price == 0) - return 0; - - if ($tax_included==1) return $price; - - // if array already read, then make a copy and use that - if ($tax_group_array) - $ret_tax_array = $tax_group_array; - else - $ret_tax_array = get_tax_group_items_as_array($tax_group); - - //print_r($ret_tax_array); - - $tax_array = get_taxes_for_item($stock_id, $ret_tax_array); - // if no exemptions or taxgroup is empty, then no included/excluded taxes - if ($tax_array == null) - return $price; - - $tax_multiplier = 0; - // loop for all items + $taxdata = array('tax_area' => $group_data['tax_area'], 'taxes' => null); - foreach ($tax_array as $taxitem) + if ($stock_id===null) // shipping special case { - $tax_multiplier += $taxitem["rate"]; + $taxdata['vat_category'] = VC_SERVICES; + $taxdata['taxes'] = get_tax_group_items_as_array(null); + return $taxdata; } - - return round($price * (1 + ($tax_multiplier / 100)), user_price_dec()); -} -//--------------------------------------------------------------------------------- -// return an array of (tax_type_id, tax_type_name, sales_gl_code, purchasing_gl_code, rate) + $item_tax_type = get_item_tax_type_for_item($stock_id); // get item tax data + $taxdata['vat_category'] = $item_tax_type['vat_category']; -function get_taxes_for_item($stock_id, $tax_group_items_array) -{ - $item_tax_type = get_item_tax_type_for_item($stock_id); - - // if the item is exempt from all taxes then return 0 + // if the item is exempt from all taxes thats all if ($item_tax_type["exempt"]) - return null; - + return $taxdata; + + $taxdata['taxes'] = array(); + // get the exemptions for this item tax type $item_tax_type_exemptions_db = get_item_tax_type_exemptions($item_tax_type["id"]); - + // read them all into an array to minimize db querying $item_tax_type_exemptions = array(); while ($item_tax_type_exemp = db_fetch($item_tax_type_exemptions_db)) { $item_tax_type_exemptions[] = $item_tax_type_exemp["tax_type_id"]; } - - $ret_tax_array = array(); - + + $tax_group_items_array = $group_data['taxes']; // if any of the taxes of the tax group are in the exemptions, then skip foreach ($tax_group_items_array as $tax_group_item) - { - + { + $skip = false; - + // if it's in the exemptions, skip - foreach ($item_tax_type_exemptions as $exemption) - { - if (($tax_group_item['tax_type_id'] == $exemption)) + if (is_null($tax_group_item['rate'])) + $skip = true; + else + foreach ($item_tax_type_exemptions as $exemption) { - $skip = true; - break; + if (($tax_group_item['tax_type_id'] == $exemption)) + { + $skip = true; + break; + } } - } - + if (!$skip) { $index = $tax_group_item['tax_type_id']; - $ret_tax_array[$index] = $tax_group_item; + $taxdata['taxes'][$index] = $tax_group_item; } } - - return $ret_tax_array; + + return $taxdata; } -//----------------------------------------------------------------------------------- -// return an array of (tax_type_id, tax_type_name, sales_gl_code, purchasing_gl_code, rate, included_in_price, Value) -function get_tax_for_items($items, $prices, $shipping_cost, $tax_group, $tax_included=null, $tax_items_array=null, $tax_algorithm = null) +/* + Main tax procedure splitting transaction item value according to item tax rules applicable: + $stock_id - stock item code; special case is '' for shipping + $amount - price/value to be splitted + $tax_group - entity tax group + $tax_included - whether value includes all taxes + $vat_factor - 0-1; tax deduction factor + $allow_reverse - option for invoice - whether to honour reverse charging + + Returned array contains calculated values for GL postings and tax registration: + 'Net' - value without tax, + 'Tax' - tax sum, + 'Cost' - cost value (can be higher then Net value) used in parallel postings, + 'vat_category' - stock tax category + and detailed info for any tax applicable tax (any array with numeric key): + 'tax_type_id' - tax type id + 'Value' - charged tax value + 'Deductible' - tax deductible (can be lower than Value for special goods) + 'Payable' - tax payable + 'Adjust' - additional adjustemnt to deductible tax due to sales structure factor + 'rate' - tax rate + 'sales_gl_code' - sales tax GL account + 'purchasing_gl_code' - purchase tax GL account + 'tax_type_name' - name of tax type +*/ +function split_item_price($stock_id, $amount, $group_id, $tax_included=false, $trans_type=ST_SUPPINVOICE, $vat_factor = 1, + $allow_reverse=true, $date=null) { - if (!$tax_algorithm) - $tax_algorithm = get_company_pref('tax_algorithm'); - // first create and set an array with all the tax types of the tax group - if($tax_items_array!=null) - $ret_tax_array = $tax_items_array; - else - $ret_tax_array = get_tax_group_items_as_array($tax_group); + global $TS; $dec = user_price_dec(); - $fully_exempt = true; - foreach($ret_tax_array as $k=>$t) + $itemdata = get_base_taxdata($stock_id, $group_id); + $vat_category = $itemdata['vat_category']; + $item_taxes = $itemdata['taxes']; + + $taxopt = $TS->options($trans_type, $itemdata['tax_area'], $vat_category, $allow_reverse); + + if (empty($item_taxes)) { - if ($t['rate'] !== null) - $fully_exempt = false; - $ret_tax_array[$k]['Net'] = 0; + $ret_array['Net'] = $amount; + $ret_array['Cost'] = $amount; + $ret_array['Tax'] = 0; + if (!is_null($item_taxes)) // register empty taxes only for not fully exempt items + $ret_array[] = array('Value'=>0, 'rate' => null, 'tax_type_id' => null, 'Deductible'=>0, 'Adjust' => 0, 'Payable' => 0); } - - $ret_tax_array['exempt'] = array('Value'=>0, 'Net'=>0, 'rate' => null, 'tax_type_id' => '', 'sales_gl_code' => ''); - $dec = user_price_dec(); - // loop for all items - for ($i = 0; $i < count($items); $i++) + else { - $item_taxes = get_taxes_for_item($items[$i], $ret_tax_array); - if ($item_taxes == null || $fully_exempt) - { - $ret_tax_array['exempt']['Value'] += round2(0, $dec); - $ret_tax_array['exempt']['Net'] += $prices[$i]; - } - else - { - $tax_multiplier = 0; + $ret_array['Net'] = $ret_array['Cost'] = $ret_array['Tax'] = 0; + + $tax_multiplier = 0; + + if ($taxopt&TAX_CHARGED) // divide tax for net and tax only if charged on document foreach ($item_taxes as $taxitem) - { $tax_multiplier += $taxitem['rate']; - } - foreach ($item_taxes as $item_tax) - { - if ($item_tax['rate'] !== null) { - $index = $item_tax['tax_type_id']; - if ($tax_included == 1) { - $ret_tax_array[$index]['Value'] += round2($prices[$i]*$item_tax['rate']/(100+$tax_multiplier), $dec); - $ret_tax_array[$index]['Net'] += round2($prices[$i]*100/(100+$tax_multiplier), $dec); + + $partial_vat_percent = get_company_pref('partial_vat_percent'); + + foreach ($item_taxes as $tax_id => $item_tax) + { + if ($item_tax['rate'] !== null) + { + + // effective vat for some special purchases is lower than nominal + $factor = $vat_category == VC_NONDEDUCT ? 0 : ($vat_category==VC_PARTIAL ? $partial_vat_percent/100.0 : 1); + + $net_value = $amount; + if ($tax_included == true) { + $vat_value = round($amount*$item_tax['rate']/(100+$tax_multiplier), 2); + + if ($taxopt&TAX_CHARGED) + $net_value -= $vat_value; + } else { - $ret_tax_array[$index]['Value'] += round2($prices[$i] * $item_tax['rate'] / 100, $dec); - $ret_tax_array[$index]['Net'] += $prices[$i]; + + $vat_value = round($amount * $item_tax['rate'] / 100, 2); } + + $ret_array['Net'] = round2($net_value, $dec); + $ret_array['Cost'] = $ret_array['Net']; + + $tax = array('Value' => 0, 'Deductible' => 0, 'Adjust' => 0, 'Payable' => 0); + + $tax['purchasing_gl_code'] = $item_tax['purchasing_gl_code']; + $tax['sales_gl_code'] = $item_tax['sales_gl_code']; + $tax['rate'] = $item_tax['rate']; + $tax['tax_type_id'] = $item_tax['tax_type_id']; + $tax['tax_type_name'] = $item_tax['tax_type_name']; + + if ($taxopt & TAX_CHARGED) // tax is charged on document + $tax['Value'] = round2($vat_value, $dec); + + if ($taxopt & TAX_PAYABLE) // transaction is taxable + $tax['Payable'] = round2($vat_value, $dec); + + if ($taxopt & TAX_DEDUCTIBLE) // tax is deductible + { + $tax['Deductible'] = round2($vat_value*$factor, 2); // [4815] To avoid rounding issues if $dec > 2 decimal places + $tax['Adjust'] = round2(-(1-$vat_factor)*$factor*$vat_value, $dec); // adjustment due to mixed taxed/exmpt sales activity + } else { + $tax['Deductible'] = 0; + $tax['Adjust'] = 0; + } + $ret_array['Cost'] += $tax['Value'] + ($tax['Payable'] - $tax['Deductible']);// - $tax['Adjust']; + + $ret_array[] = $tax; + $ret_array['Tax'] += $tax['Value']; } - } } } - // add the shipping taxes, only if non-zero, and only if tax group taxes shipping - if ($shipping_cost != 0) + $ret_array['vat_category'] = $vat_category; + return $ret_array; +} + +//----------------------------------------------------------------------------------- +// return an array of (tax_type_id, tax_type_name, sales_gl_code, purchasing_gl_code, rate, included_in_price, Value, Net) +// +// $vat_factors - effective part of vat values included in tax; calculated but not included vat is added to net value +// +function get_tax_for_items($trans_type, $items, $prices, $shipping_cost, $tax_group, $tax_included=null, + $tax_algorithm = null, $vat_factors = null, $allow_reverse = true) +{ + + // if shipping cost is passed, just add to the prices/items tables + if ($shipping_cost != 0) + { + $items[] = null; + $prices[] = $shipping_cost; + if ($vat_factors) + $vat_factors[] = 1; + } + + // calculate tax sums + $ret_tax_array = array(); + foreach($items as $i => $stock_id) { - $item_taxes = get_shipping_tax_as_array($tax_group); - if ($item_taxes != null) + $taxdata = split_item_price($stock_id, $prices[$i], $tax_group, $tax_included, $trans_type, + $vat_factors ? $vat_factors[$i] : 1, $allow_reverse, $date=null); // FIXME: $date + + foreach ($taxdata as $key => $data) { - if ($tax_included == 1) + if (is_numeric($key)) { - $tax_rate = 0; - foreach ($item_taxes as $item_tax) + $tax_id = isset($data['tax_type_id']) ? $data['tax_type_id'] : 'exempt'; + + if (!isset($ret_tax_array[$tax_id])) { - $index = $item_tax['tax_type_id']; - if(isset($ret_tax_array[$index])) { - $tax_rate += $item_tax['rate']; - } + $ret_tax_array[$tax_id] = $data; + $ret_tax_array[$tax_id]['Net'] = $taxdata['Net']; + $ret_tax_array[$tax_id]['vat_category'] = $taxdata['vat_category']; } - $shipping_net = round2($shipping_cost*100/(100+$tax_rate), $dec); - } - foreach ($item_taxes as $item_tax) - { - $index = $item_tax['tax_type_id']; - if ($item_tax['rate'] !== null && $ret_tax_array[$index]['rate'] !== null) { - if($tax_included==1) { - $ret_tax_array[$index]['Value'] += round2($shipping_cost*$item_tax['rate']/(100+$tax_rate), $dec); - $ret_tax_array[$index]['Net'] += $shipping_net; - } else { - $ret_tax_array[$index]['Value'] += round2($shipping_cost * $item_tax['rate'] / 100, $dec); - $ret_tax_array[$index]['Net'] += $shipping_cost; - } + else + { + foreach(array('Value', 'Payable', 'Deductible', 'Adjust') as $amt) + $ret_tax_array[$tax_id][$amt] += $data[$amt]; + $ret_tax_array[$tax_id]['Net'] += $taxdata['Net']; } } } } - if ($tax_algorithm == TCA_TOTALS ) { + if (!$tax_algorithm) + $tax_algorithm = get_company_pref('tax_algorithm'); + + if ($tax_algorithm == TCA_TOTALS) { // ? + $dec = user_price_dec(); // update taxes with foreach($ret_tax_array as $index => $item_tax) { - $ret_tax_array[$index]['Value'] = round2($item_tax['Net'] * $item_tax['rate'] / 100, $dec); + if ($ret_tax_array[$index]['Value']) + $ret_tax_array[$index]['Value'] = round2($item_tax['Net'] * $item_tax['rate'] / 100, $dec); } } return $ret_tax_array; } + +//--------------------------------------------------------------------------------- + +// returns the price of a given item minus any included taxes +// for item $stock_id with line price $price and supplier/customer group_id $tax_group + +function get_tax_free_price_for_item($trans_type, $stock_id, $price, $tax_group, $tax_included, $allow_reverse = true) +{ + // if price is zero, then can't be taxed ! + if ($price == 0) + return 0; + + if ($tax_included==0) return $price; + + $taxdata = split_item_price($stock_id, $price, $tax_group, $tax_included, $trans_type, 1, $allow_reverse); + + return $taxdata['Net']; +} +// +// Full price (incl. VAT) for item $stock_id +// calculated for line price $price, and applicable group $tax_group +// +function get_full_price_for_item($trans_type, $stock_id, $price, $tax_group, $tax_included, $allow_reverse = true) +{ + // if price is zero, then can't be taxed ! + if ($price == 0) + return 0; + + if ($tax_included==1) return $price; + + $taxdata = split_item_price($stock_id, $price, $tax_group, $tax_included, $trans_type, 1, $allow_reverse); + + return $taxdata['Net'] + $taxdata['Tax']; +} diff --git a/taxes/tax_groups.php b/taxes/tax_groups.php index 16873e04..451c344a 100644 --- a/taxes/tax_groups.php +++ b/taxes/tax_groups.php @@ -59,12 +59,12 @@ if ($Mode=='ADD_ITEM' || $Mode=='UPDATE_ITEM') } if ($selected_id != -1) { - update_tax_group($selected_id, $_POST['name'], $taxes, $tax_shippings); + update_tax_group($selected_id, $_POST['name'], $taxes, $tax_shippings, get_post('tax_area')); display_notification(_('Selected tax group has been updated')); } else { - add_tax_group($_POST['name'], $taxes, $tax_shippings); + add_tax_group($_POST['name'], $taxes, $tax_shippings, get_post('tax_area')); display_notification(_('New tax group has been added')); } @@ -123,7 +123,7 @@ $result = get_all_tax_groups(check_value('show_inactive')); start_form(); start_table(TABLESTYLE); -$th = array(_("Description"), "", ""); +$th = array(_("Description"), _("Tax Area Type"), "", ""); inactive_control_column($th); table_header($th); @@ -135,6 +135,7 @@ while ($myrow = db_fetch($result)) alt_table_row_color($k); label_cell($myrow["name"]); + label_cell($tax_area_types[$myrow['tax_area']]); inactive_control_cell($myrow["id"], $myrow["inactive"], 'tax_groups', 'id'); edit_button_cell("Edit".$myrow["id"], _("Edit")); @@ -157,12 +158,14 @@ if ($selected_id != -1) $group = get_tax_group($selected_id); $_POST['name'] = $group["name"]; + $_POST['tax_area'] = $group["tax_area"]; } hidden('selected_id', $selected_id); } text_row_ex(_("Description:"), 'name', 40); +vat_areas_list_row(_("Tax Area Type:"), 'tax_area'); end_table(); diff --git a/taxes/tax_rules.inc b/taxes/tax_rules.inc new file mode 100644 index 00000000..73f30b64 --- /dev/null +++ b/taxes/tax_rules.inc @@ -0,0 +1,160 @@ +. +***********************************************************************/ +/* + FA tax rules basic reimplementation. Final version would involve changes in database scheme, + and/or tax system class selection according to site requirements. + + TODO: + . changes in Sales module + . change all tax related methods in supp_trans to use split_item_price instead of other functions like get_taxes_for_item + . all tax types selections in tax group table (beside domestic group) are currently ignored (see FIXME in split_item_price()) +*/ +define('TAX_NONE', 0); // none option +define('TQ_NONE', 0); // none option + +// implemented taxation options +// basic: +define('TAX_PAYABLE', 1); +define('TAX_DEDUCTIBLE', 2); +define('TAX_CHARGED', 4); +// category depenedent: +define('TAX_DUEDATE', 8); +define('TAX_PARTIAL', 16); + +// quirks dependent on vat_categories +define('TQ_NONDEDUCT', 1); // never deductible +define('TQ_PARTIAL', 2); // partially deductible (using global factor) +define('TQ_DUEDATE', 4); // taxable on due date instead of transaction date +define('TQ_REVERSE', 8); // tax is reverse charged +define('TQ_IMPDEDUCTIBLE', 16); // import tax is deductible + +class tax_system +{ + //-------------------------- entity taxation rules ---------------------------- + // general rules: who and when pays taxes + // + // FIXME: credit notes + // + var $description; + var $ts_basic_rules; + var $ts_category_quirks; + + function __construct() + { + $this->ts_basic_rules = array( + TA_DOMESTIC => array( + ST_SALESQUOTE => TAX_CHARGED, + ST_SALESORDER => TAX_CHARGED, + ST_CUSTDELIVERY => TAX_CHARGED, + ST_SALESINVOICE => TAX_PAYABLE | TAX_CHARGED, + ST_CUSTCREDIT => TAX_PAYABLE | TAX_CHARGED, + ST_PURCHORDER => TAX_CHARGED, + ST_SUPPRECEIVE => TAX_CHARGED, + ST_SUPPINVOICE => TAX_DEDUCTIBLE | TAX_CHARGED, + ST_SUPPCREDIT => TAX_DEDUCTIBLE | TAX_CHARGED, + ), + TA_EXPORT => array( + ST_SALESQUOTE => TAX_NONE, + ST_SALESORDER => TAX_NONE, + ST_CUSTDELIVERY => TAX_NONE, + ST_SALESINVOICE => TAX_NONE, + ST_CUSTCREDIT => TAX_NONE, + ST_PURCHORDER => TAX_NONE, + ST_SUPPRECEIVE => TAX_NONE, + ST_SUPPINVOICE => TAX_NONE, + ST_SUPPCREDIT => TAX_NONE, + ), + TA_EU => array( + ST_SALESQUOTE => TAX_NONE, + ST_SALESORDER => TAX_NONE, + ST_CUSTDELIVERY => TAX_NONE, + ST_SALESINVOICE => TAX_NONE, + ST_CUSTCREDIT => TAX_NONE, + ST_PURCHORDER => TAX_NONE, + ST_SUPPRECEIVE => TAX_NONE, + ST_SUPPINVOICE => TAX_DEDUCTIBLE | TAX_PAYABLE, + ST_SUPPCREDIT => TAX_DEDUCTIBLE | TAX_PAYABLE, + ), + ); + + //-------------------------- special goods dependent rules ---------------------------- + // + $this->ts_category_quirks = array( + VC_OTHER => TQ_NONE, + VC_MEDIA => TQ_DUEDATE, // is no longer used ? + VC_ASSETS => TQ_NONE, // just separate category in tax reg + VC_NONDEDUCT => TQ_NONDEDUCT, + VC_SERVICES => TQ_IMPDEDUCTIBLE, + VC_PARTIAL => TQ_PARTIAL, + VC_REVERSE => TQ_REVERSE, + ); + } + /* + Returns tax options applicable for the arguments set + + $allow_reverse - decides whether reverse charging option is honoured for sales invoice + (this depends on 'continue transaction' value as defined by law, so have to be set by case). + */ + function options($trans_type, $tax_area, $vat_category, $allow_reverse=true) + { + if (!isset($vat_category)) // exempt goods has really no category + return TAX_NONE; + + $options = $this->ts_basic_rules[$tax_area][$trans_type]; + + // per vat category quirks + $quirks = $this->ts_category_quirks[$vat_category]; + + if ($quirks & TQ_DUEDATE) + $options |= TAX_DUEDATE; + + if ($quirks & TQ_NONDEDUCT) + $options &= ~TAX_DEDUCTIBLE; + + if ($quirks & TQ_PARTIAL) + if ($options & TAX_DEDUCTIBLE) + $options |= TAX_PARTIAL; + + if ($quirks & TQ_IMPDEDUCTIBLE) + if ($tax_area == TA_EXPORT && in_array($trans_type, array(ST_SUPPINVOICE, ST_SUPPCREDIT))) + { + $options |= TAX_DEDUCTIBLE | TAX_PAYABLE; + } + + if (($quirks & TQ_REVERSE) && (!in_array($trans_type,array(ST_SALESINVOICE, ST_CUSTCREDIT)) || $allow_reverse)) + if ($tax_area == TA_DOMESTIC) + { + $options ^= TAX_PAYABLE; + $options &= ~TAX_CHARGED; + } + + return $options; + } +}; + +function dbg_tax($options) +{ + $dbg_rules = array( + TAX_PAYABLE => 'payable', + TAX_DEDUCTIBLE => 'deductible', + TAX_CHARGED => 'charged', + TAX_DUEDATE => 'due_date', + TAX_PARTIAL => 'partial' + ); + + $opts = array(); + foreach($dbg_rules as $key => $name) + if ($options & $key) + $opts[] = $name; + + _vd(implode($opts, ',')); +}