From 6b07d8ee211f0bf332f63b6fdb8abb518eb24b7c Mon Sep 17 00:00:00 2001 From: FrancescoUK Date: Tue, 4 Feb 2020 21:25:37 +0000 Subject: [PATCH] Fix wrong tax amount with tax included --- application/config/migration.php | 2 +- application/libraries/Sale_lib.php | 15 +- application/libraries/Tax_lib.php | 44 ++- .../20170502221506_sales_tax_data.php | 18 +- .../migrations/20190129212600_indiagst.php | 2 +- .../migrations/20200202000000_taxamount.php | 280 ++++++++++++++++++ 6 files changed, 319 insertions(+), 42 deletions(-) create mode 100644 application/migrations/20200202000000_taxamount.php diff --git a/application/config/migration.php b/application/config/migration.php index 9d19b9c11..0b0416b42 100644 --- a/application/config/migration.php +++ b/application/config/migration.php @@ -69,7 +69,7 @@ $config['migration_auto_latest'] = FALSE; | be upgraded / downgraded to. | */ -$config['migration_version'] = 20171126100000; +$config['migration_version'] = 20200202000000; /* |-------------------------------------------------------------------------- diff --git a/application/libraries/Sale_lib.php b/application/libraries/Sale_lib.php index 4f6fe6115..abaad6e79 100644 --- a/application/libraries/Sale_lib.php +++ b/application/libraries/Sale_lib.php @@ -545,7 +545,6 @@ class Sale_lib return $totals; } - // Multiple Payments public function get_amount_due() { @@ -910,16 +909,16 @@ class Sale_lib public function get_quantity_already_added($item_id, $item_location) { $items = $this->get_cart(); - $quanity_already_added = 0; + $quantity_already_added = 0; foreach($items as $item) { if($item['item_id'] == $item_id && $item['item_location'] == $item_location) { - $quanity_already_added+=$item['quantity']; + $quantity_already_added += $item['quantity']; } } - return $quanity_already_added; + return $quantity_already_added; } public function get_item_id($line_to_get) @@ -1209,8 +1208,7 @@ class Sale_lib $total = bcmul($quantity, $price); if($discount_type == PERCENT) { - $discount_fraction = bcdiv($discount, 100); - $discount=bcmul($total,$discount_fraction); + $discount = bcmul($total, bcdiv($discount, 100)); } return round($discount, totals_decimals(), PHP_ROUND_HALF_UP); @@ -1219,14 +1217,15 @@ class Sale_lib public function get_item_tax($quantity, $price, $discount, $discount_type, $tax_percentage) { $price = $this->get_item_total($quantity, $price, $discount, $discount_type, TRUE); + if($this->CI->config->item('tax_included')) { - $tax_fraction = bcadd(100, $tax_percentage); - $tax_fraction = bcdiv($tax_fraction, 100); + $tax_fraction = bcdiv(bcadd(100, $tax_percentage), 100); $price_tax_excl = bcdiv($price, $tax_fraction); return bcsub($price, $price_tax_excl); } + $tax_fraction = bcdiv($tax_percentage, 100); return bcmul($price, $tax_fraction); diff --git a/application/libraries/Tax_lib.php b/application/libraries/Tax_lib.php index a488f5deb..a66487d46 100644 --- a/application/libraries/Tax_lib.php +++ b/application/libraries/Tax_lib.php @@ -44,14 +44,11 @@ class Tax_lib */ public function get_tax_for_amount($tax_basis, $tax_percentage, $rounding_mode, $decimals) { - $tax_fraction = bcdiv($tax_percentage, 100); - - $tax_amount = bcmul($tax_basis, $tax_fraction); + $tax_amount = bcmul($tax_basis, bcdiv($tax_percentage, 100)); return Rounding_mode::round_number($rounding_mode, $tax_amount, $decimals); } - /** * Compute taxes for all items in the cart */ @@ -89,7 +86,7 @@ class Tax_lib if($this->CI->config->item('tax_included')) { $tax_type = Tax_lib::TAX_TYPE_INCLUDED; - $tax_amount = $this->get_included_tax($item['quantity'], $item['price'], $item['discount'], $tax['percent'], $tax_decimals, Rounding_mode::HALF_UP); + $tax_amount = $this->get_included_tax($item['quantity'], $item['price'], $item['discount'], $item['discount_type'], $tax['percent'], $tax_decimals, Rounding_mode::HALF_UP); } else { @@ -124,13 +121,14 @@ class Tax_lib { // Start of destination based tax calculations - if ($item['tax_category_id'] == NULL) + if($item['tax_category_id'] == NULL) { $item['tax_category_id'] = $this->CI->config->config['default_tax_category']; } $taxed = $this->apply_destination_tax($item, $customer_info->city, $customer_info->state, $customer_info->sales_tax_code_id, $register_mode, 0, $taxes, $item_taxes, $item['line']); } + if($taxed) { $cart[$line]['taxed_flag'] = $this->CI->lang->line('sales_taxed_ind'); @@ -150,18 +148,13 @@ class Tax_lib return $tax_details; } - public function get_included_tax($quantity, $price, $discount_percentage, $tax_percentage, $tax_decimal, $rounding_code) + public function get_included_tax($quantity, $price, $discount_percentage, $discount_type, $tax_percentage, $tax_decimal, $rounding_code) { - $price = $this->CI->sale_lib->get_item_total($quantity, $price, $discount_percentage, TRUE); - $tax_fraction = bcadd(100, $tax_percentage); - $tax_fraction = bcdiv($tax_fraction, 100); - $price_tax_excl = bcdiv($price, $tax_fraction); - $tax_amount = bcsub($price, $price_tax_excl); + $tax_amount = $this->CI->sale_lib->get_item_tax($quantity, $price, $discount_percentage, $discount_type, $tax_percentage); return Rounding_mode::round_number($rounding_code, $tax_amount, $tax_decimal); } - /* * Updates the sales_tax array which is later saved to the `sales_taxes` table and used for printing taxes on receipts and invoices */ @@ -249,7 +242,6 @@ class Tax_lib $decimals = totals_decimals(); } - foreach($taxes as $row_number => $sales_tax) { $tax_amount = $sales_tax['sale_tax_amount']; @@ -275,12 +267,12 @@ class Tax_lib elseif($rounding_code == Rounding_mode::ROUND_UP) { $fig = (int)str_pad('1', $decimals, '0'); - $rounded_tax_amount = (ceil($tax_amount * $fig) / $fig); + $rounded_tax_amount = ceil($tax_amount * $fig) / $fig; } elseif($rounding_code == Rounding_mode::ROUND_DOWN) { $fig = (int)str_pad('1', $decimals, '0'); - $rounded_tax_amount = (floor($tax_amount * $fig) / $fig); + $rounded_tax_amount = floor($tax_amount * $fig) / $fig; } elseif($rounding_code == Rounding_mode::HALF_FIVE) { @@ -289,10 +281,8 @@ class Tax_lib $taxes[$row_number]['sale_tax_amount'] = $rounded_tax_amount; } - } - /** * Determine the applicable tax code and then determine the tax amount to be applied. * If a tax amount was identified then accumulate into the sales_taxes array @@ -336,7 +326,7 @@ class Tax_lib if($tax_type == Tax_lib::TAX_TYPE_INCLUDED) { - $tax_amount = $this->get_included_tax($item['quantity'], $item['price'], $item['discount'], $tax_rate, $tax_decimals, $rounding_code); + $tax_amount = $this->get_included_tax($item['quantity'], $item['price'], $item['discount'], $item['discount_type'], $tax_rate, $tax_decimals, $rounding_code); } else { @@ -366,17 +356,13 @@ class Tax_lib $item_taxes[] = $item_taxes_detail; } - return $taxed; - } - else - { - return $taxed; } + + return $taxed; } public function get_applicable_tax_code($register_mode, $city, $state, $sales_tax_code_id) { - if($register_mode == "sale") { $sales_tax_code_id = $this->CI->config->config['default_tax_code']; // overrides customer assigned code @@ -387,7 +373,8 @@ class Tax_lib { $sales_tax_code_id = $this->CI->Tax_code->get_sales_tax_code($city, $state); - if($sales_tax_code_id == NULL || $sales_tax_code_id == 0) { + if($sales_tax_code_id == NULL || $sales_tax_code_id == 0) + { $sales_tax_code_id = $this->CI->config->config['default_tax_code']; // overrides customer assigned code } } @@ -414,6 +401,7 @@ class Tax_lib $b = $tax_code['tax_code_name']; $tax_code_options[$a] = $b; } + return $tax_code_options; } @@ -428,6 +416,7 @@ class Tax_lib $b = $tax_jurisdiction['jurisdiction_name']; $tax_jurisdiction_options[$a] = $b; } + return $tax_jurisdiction_options; } @@ -443,6 +432,7 @@ class Tax_lib $tax_category_options[$a] = $b; } + return $tax_category_options; } @@ -461,9 +451,9 @@ class Tax_lib { $s2 = $selected; } + return ''; } - } ?> diff --git a/application/migrations/20170502221506_sales_tax_data.php b/application/migrations/20170502221506_sales_tax_data.php index af9a2144a..e25af45a4 100644 --- a/application/migrations/20170502221506_sales_tax_data.php +++ b/application/migrations/20170502221506_sales_tax_data.php @@ -1,4 +1,5 @@ get_count_of_unmigrated(); - error_log('Migrating sales tax history. The number of sales that will be migrated is '.$number_of_unmigrated); + error_log('Migrating sales tax history. The number of sales that will be migrated is ' . $number_of_unmigrated); if($number_of_unmigrated > 0) { $unmigrated_invoices = $this->get_unmigrated($number_of_unmigrated)->result_array(); @@ -26,11 +27,13 @@ class Migration_Sales_Tax_Data extends CI_Migration $this->upgrade_tax_history_for_sale($unmigrated_invoice['sale_id']); } } - error_log('Migrating sales tax history. The number of sales that will be migrated is finished.'); + error_log('Migrating sales tax history. The number of sales that will be migrated is finished.'); } + public function down() { } + private function upgrade_tax_history_for_sale($sale_id) { $CI =& get_instance(); @@ -74,6 +77,7 @@ class Migration_Sales_Tax_Data extends CI_Migration $this->round_sales_taxes($sales_taxes); $this->save_sales_tax($sales_taxes); } + private function get_unmigrated($block_count) { $this->db->select('SIT.sale_id'); @@ -87,6 +91,7 @@ class Migration_Sales_Tax_Data extends CI_Migration $this->db->limit($block_count); return $this->db->get(); } + private function get_sale_items_for_migration($sale_id) { $this->db->select('sales_items.sale_id as sale_id'); @@ -101,6 +106,7 @@ class Migration_Sales_Tax_Data extends CI_Migration $this->db->where('sales_items.sale_id', $sale_id); return $this->db->get(); } + private function get_count_of_unmigrated() { $result = $this->db->query('SELECT COUNT(*) FROM(SELECT SIT.sale_id, ST.sale_id as sales_taxes_sale_id FROM ' @@ -111,6 +117,7 @@ class Migration_Sales_Tax_Data extends CI_Migration . ' ORDER BY SIT.sale_id) as US')->result_array(); return $result[0]['COUNT(*)']; } + private function update_sales_items_taxes_amount($sale_id, $line, $name, $percent, $tax_type, $item_tax_amount) { $this->db->where('sale_id', $sale_id); @@ -119,6 +126,7 @@ class Migration_Sales_Tax_Data extends CI_Migration $this->db->where('percent', $percent); $this->db->update('sales_items_taxes', array('tax_type' => $tax_type, 'item_tax_amount' => $item_tax_amount)); } + private function save_sales_tax(&$sales_taxes) { foreach($sales_taxes as $line=>$sales_tax) @@ -142,7 +150,7 @@ class Migration_Sales_Tax_Data extends CI_Migration { $total = bcmul($quantity, $price); $discount_fraction = bcdiv($discount, 100); - $discount=bcmul($total,$discount_fraction); + $discount = bcmul($total, $discount_fraction); return round($discount, totals_decimals(), PHP_ROUND_HALF_UP); } @@ -190,7 +198,7 @@ class Migration_Sales_Tax_Data extends CI_Migration } else { - $rounded_total = round ( $amount, $decimals, $rounding_mode); + $rounded_total = round($amount, $decimals, $rounding_mode); } return $rounded_total; @@ -294,4 +302,4 @@ class Migration_Sales_Tax_Data extends CI_Migration } } } -?> \ No newline at end of file +?> diff --git a/application/migrations/20190129212600_indiagst.php b/application/migrations/20190129212600_indiagst.php index 225e8df83..dbded8191 100644 --- a/application/migrations/20190129212600_indiagst.php +++ b/application/migrations/20190129212600_indiagst.php @@ -9,7 +9,7 @@ class Migration_IndiaGST extends CI_Migration public function up() { - if (!$this->db->field_exists('sales_tax_code', 'customers')) + if(!$this->db->field_exists('sales_tax_code', 'customers')) { return; } diff --git a/application/migrations/20200202000000_taxamount.php b/application/migrations/20200202000000_taxamount.php new file mode 100644 index 000000000..30fffa3bc --- /dev/null +++ b/application/migrations/20200202000000_taxamount.php @@ -0,0 +1,280 @@ +load->library('tax_lib'); + } + + public function up() + { + $CI =& get_instance(); + $tax_included = $CI->Appconfig->get('tax_included', Migration_TaxAmount::YES) == Migration_TaxAmount::YES; + + if($tax_included) + { + $tax_decimals = $CI->Appconfig->get('tax_decimals', 2); + $number_of_unmigrated = $this->get_count_of_unmigrated(); + error_log('Migrating sales tax fixing. The number of sales that will be migrated is ' . $number_of_unmigrated); + if($number_of_unmigrated > 0) + { + $unmigrated_invoices = $this->get_unmigrated($number_of_unmigrated)->result_array(); + $this->db->query('RENAME TABLE ' . $this->db->dbprefix('sales_taxes') . ' TO ' . $this->db->dbprefix('sales_taxes_backup')); + $this->db->query('CREATE TABLE ' . $this->db->dbprefix('sales_taxes') . ' LIKE ' . $this->db->dbprefix('sales_taxes_backup')); + foreach($unmigrated_invoices as $key=>$unmigrated_invoice) + { + $this->upgrade_tax_history_for_sale($unmigrated_invoice['sale_id'], $tax_decimals, $tax_included); + } + $this->db->query('DROP TABLE ' . $this->db->dbprefix('sales_taxes_backup')); + } + error_log('Migrating sales tax fixing. The number of sales that will be migrated is finished.'); + } + } + + public function down() + { + } + + private function upgrade_tax_history_for_sale($sale_id, $tax_decimals, $tax_included) + { + $customer_sales_tax_support = FALSE; + $tax_type = Migration_TaxAmount::VAT_TAX; + $sales_taxes = array(); + $tax_group_sequence = 0; + $items = $this->get_sale_items_for_migration($sale_id)->result_array(); + foreach($items as $item) + { + // This computes tax for each line item and adds it to the tax type total + $tax_group = (float)$item['percent'] . '% ' . $item['name']; + $tax_basis = $this->get_item_total($item['quantity_purchased'], $item['item_unit_price'], $item['discount'], TRUE); + $item_tax_amount = $this->get_item_tax($tax_basis, $item['percent'], PHP_ROUND_HALF_UP, $tax_decimals); + $this->update_sales_items_taxes_amount($sale_id, $item['line'], $item['name'], $item['percent'], $tax_type, $item_tax_amount); + $this->update_sales_taxes($sales_taxes, $tax_type, $tax_group, $item['percent'], $tax_basis, $item_tax_amount, $tax_group_sequence, PHP_ROUND_HALF_UP, $sale_id, $item['name']); + $tax_group_sequence += 1; + } + // Not sure when this would ever kick in, but this is technically the correct logic. + if($customer_sales_tax_support) + { + $this->apply_invoice_taxing($sales_taxes); + } + $this->round_sales_taxes($sales_taxes); + $this->save_sales_tax($sales_taxes); + } + + private function get_unmigrated($block_count) + { + $this->db->select('SIT.sale_id'); + $this->db->select('ST.sale_id as sales_taxes_sale_id'); + $this->db->from('sales_items_taxes as SIT'); + $this->db->join('sales_taxes as ST', 'SIT.sale_id = ST.sale_id', 'left'); + $this->db->group_by('SIT.sale_id'); + $this->db->group_by('ST.sale_id'); + $this->db->order_by('SIT.sale_id'); + $this->db->limit($block_count); + return $this->db->get(); + } + + private function get_count_of_unmigrated() + { + $result = $this->db->query('SELECT COUNT(*) FROM(SELECT SIT.sale_id, ST.sale_id as sales_taxes_sale_id FROM ' + . $this->db->dbprefix('sales_items_taxes') + . ' as SIT LEFT JOIN ' + . $this->db->dbprefix('sales_taxes') + . ' as ST ON SIT.sale_id = ST.sale_id GROUP BY SIT.sale_id, ST.sale_id' + . ' ORDER BY SIT.sale_id) as US')->result_array(); + return $result[0]['COUNT(*)']; + } + + private function get_sale_items_for_migration($sale_id) + { + $this->db->select('sales_items.sale_id as sale_id'); + $this->db->select('sales_items.line as line'); + $this->db->select('item_unit_price'); + $this->db->select('discount'); + $this->db->select('quantity_purchased'); + $this->db->select('percent'); + $this->db->select('name'); + $this->db->from('sales_items as sales_items'); + $this->db->join('sales_items_taxes as sales_items_taxes', 'sales_items.sale_id = sales_items_taxes.sale_id and sales_items.line = sales_items_taxes.line'); + $this->db->where('sales_items.sale_id', $sale_id); + return $this->db->get(); + } + + private function update_sales_items_taxes_amount($sale_id, $line, $name, $percent, $tax_type, $item_tax_amount) + { + $this->db->where('sale_id', $sale_id); + $this->db->where('line', $line); + $this->db->where('name', $name); + $this->db->where('percent', $percent); + $this->db->update('sales_items_taxes', array('tax_type' => $tax_type, 'item_tax_amount' => $item_tax_amount)); + } + + private function save_sales_tax(&$sales_taxes) + { + foreach($sales_taxes as $line=>$sales_tax) + { + $this->db->insert('sales_taxes', $sales_tax); + } + } + + public function get_item_total($quantity, $price, $discount, $include_discount = FALSE) + { + $total = bcmul($quantity, $price); + + if($include_discount) + { + $total = bcsub($total, bcmul(bcmul($quantity, $price), bcdiv($discount, 100))); + } + + return $total; + } + + public function get_item_tax($tax_basis, $tax_percentage, $rounding_mode, $decimals) + { + $tax_fraction = bcdiv(bcadd(100, $tax_percentage), 100); + $price_tax_excl = bcdiv($tax_basis, $tax_fraction); + $tax_amount = bcsub($tax_basis, $price_tax_excl); + + return $this->round_number($rounding_mode, $tax_amount, $decimals); + } + + public function get_sales_tax_for_amount($tax_basis, $tax_percentage, $rounding_mode, $decimals) + { + $tax_fraction = bcdiv($tax_percentage, 100); + $tax_amount = bcmul($tax_basis, $tax_fraction); + + return $this->round_number($rounding_mode, $tax_amount, $decimals); + } + + public function round_number($rounding_mode, $amount, $decimals) + { + if($rounding_mode == Migration_TaxAmount::ROUND_UP) + { + $fig = pow(10,$decimals); + $rounded_total = (ceil($fig*$amount) + ceil($fig*$amount - ceil($fig*$amount)))/$fig; + } + elseif($rounding_mode == Migration_TaxAmount::ROUND_DOWN) + { + $fig = pow(10,$decimals); + $rounded_total = (floor($fig*$amount) + floor($fig*$amount - floor($fig*$amount)))/$fig; + } + elseif($rounding_mode == Migration_TaxAmount::HALF_FIVE) + { + $rounded_total = round($amount / 5) * 5; + } + else + { + $rounded_total = round($amount, $decimals, $rounding_mode); + } + + return $rounded_total; + } + + public function update_sales_taxes(&$sales_taxes, $tax_type, $tax_group, $tax_rate, $tax_basis, $item_tax_amount, $tax_group_sequence, $rounding_code, $sale_id, $name='', $tax_code='') + { + $tax_group_index = $this->clean('X'.$tax_group); + if(!array_key_exists($tax_group_index, $sales_taxes)) + { + $insertkey = $tax_group_index; + $sales_tax = array($insertkey => array( + 'sale_id' => $sale_id, + 'tax_type' => $tax_type, + 'tax_group' => $tax_group, + 'sale_tax_basis' => $tax_basis, + 'sale_tax_amount' => $item_tax_amount, + 'print_sequence' => $tax_group_sequence, + 'name' => $name, + 'tax_rate' => $tax_rate, + 'sales_tax_code_id' => $tax_code, + 'rounding_code' => $rounding_code + )); + //add to existing array + $sales_taxes += $sales_tax; + } + else + { + // Important ... the sales amounts are accumulated for the group at the maximum configurable scale value of 4 + // but the scale will in reality be the scale specified by the tax_decimal configuration value used for sales_items_taxes + $sales_taxes[$tax_group_index]['sale_tax_basis'] = bcadd($sales_taxes[$tax_group_index]['sale_tax_basis'], $tax_basis, 4); + $sales_taxes[$tax_group_index]['sale_tax_amount'] = bcadd($sales_taxes[$tax_group_index]['sale_tax_amount'], $item_tax_amount, 4); + } + } + + public function clean($string) + { + $string = str_replace(' ', '-', $string); // Replaces all spaces with hyphens. + + return preg_replace('/[^A-Za-z0-9\-]/', '', $string); // Removes special chars. + } + + public function apply_invoice_taxing(&$sales_taxes) + { + if(!empty($sales_taxes)) + { + $sort = array(); + foreach($sales_taxes as $k => $v) + { + $sort['print_sequence'][$k] = $v['print_sequence']; + } + array_multisort($sort['print_sequence'], SORT_ASC, $sales_taxes); + } + $decimals = totals_decimals(); + foreach($sales_taxes as $row_number => $sales_tax) + { + $sales_taxes[$row_number]['sale_tax_amount'] = $this->get_sales_tax_for_amount($sales_tax['sale_tax_basis'], $sales_tax['tax_rate'], $sales_tax['rounding_code'], $decimals); + } + } + + public function round_sales_taxes(&$sales_taxes) + { + if(!empty($sales_taxes)) + { + $sort = array(); + foreach($sales_taxes as $k=>$v) + { + $sort['print_sequence'][$k] = $v['print_sequence']; + } + array_multisort($sort['print_sequence'], SORT_ASC, $sales_taxes); + } + $decimals = totals_decimals(); + foreach($sales_taxes as $row_number => $sales_tax) + { + $sale_tax_amount = $sales_tax['sale_tax_amount']; + $rounding_code = $sales_tax['rounding_code']; + $rounded_sale_tax_amount = $sale_tax_amount; + + if ($rounding_code == PHP_ROUND_HALF_UP + || $rounding_code == PHP_ROUND_HALF_DOWN + || $rounding_code == PHP_ROUND_HALF_EVEN + || $rounding_code == PHP_ROUND_HALF_ODD) + { + $rounded_sale_tax_amount = round($sale_tax_amount, $decimals, $rounding_code); + } + elseif($rounding_code == Migration_TaxAmount::ROUND_UP) + { + $fig = (int) str_pad('1', $decimals, '0'); + $rounded_sale_tax_amount = (ceil($sale_tax_amount * $fig) / $fig); + } + elseif($rounding_code == Migration_TaxAmount::ROUND_DOWN) + { + $fig = (int) str_pad('1', $decimals, '0'); + $rounded_sale_tax_amount = (floor($sale_tax_amount * $fig) / $fig); + } + elseif($rounding_code == Migration_TaxAmount::HALF_FIVE) + { + $rounded_sale_tax_amount = round($sale_tax_amount / 5) * 5; + } + $sales_taxes[$row_number]['sale_tax_amount'] = $rounded_sale_tax_amount; + } + } +} +?>