diff --git a/application/config/autoload.php b/application/config/autoload.php index 1dd9072e6..e039a5c28 100644 --- a/application/config/autoload.php +++ b/application/config/autoload.php @@ -132,4 +132,4 @@ $autoload['language'] = array(); | | $autoload['model'] = array('first_model' => 'first'); */ -$autoload['model'] = array('Appconfig', 'Person', 'Customer', 'Employee', 'Module', 'Item', 'Item_taxes', 'Sale', 'Sale_suspended', 'Supplier', 'Inventory', 'Receiving', 'Giftcard', 'Item_kit', 'Item_kit_items', 'Stock_location', 'Item_quantity','Dinner_table','Customer_rewards','Rewards'); +$autoload['model'] = array('Appconfig', 'Person', 'Customer', 'Employee', 'Module', 'Item', 'Item_taxes', 'Sale', 'Sale_suspended', 'Supplier', 'Inventory', 'Receiving', 'Giftcard', 'Item_kit', 'Item_kit_items', 'Stock_location', 'Item_quantity','Dinner_table','Customer_rewards','Rewards', 'Tax'); diff --git a/application/controllers/Config.php b/application/controllers/Config.php index 103115b1b..0fc114602 100644 --- a/application/controllers/Config.php +++ b/application/controllers/Config.php @@ -199,6 +199,7 @@ class Config extends Secure_Controller $data['logo_exists'] = $this->config->item('company_logo') != ''; $data['line_sequence_options'] = $this->sale_lib->get_line_sequence_options(); $data['register_mode_options'] = $this->sale_lib->get_register_mode_options(); + $data['rounding_options'] = Rounding_code::get_rounding_options(); $data = $this->xss_clean($data); @@ -252,6 +253,8 @@ class Config extends Secure_Controller 'default_tax_2_rate' => parse_decimals($this->input->post('default_tax_2_rate')), 'default_tax_2_name' => $this->input->post('default_tax_2_name'), 'tax_included' => $this->input->post('tax_included') != NULL, + 'customer_sales_tax_support' => $this->input->post('customer_sales_tax_support') != NULL, + 'default_origin_tax_code' => $this->input->post('default_origin_tax_code'), 'receiving_calculate_average_price' => $this->input->post('receiving_calculate_average_price') != NULL, 'lines_per_page' => $this->input->post('lines_per_page'), 'default_sales_discount' => $this->input->post('default_sales_discount'), @@ -312,7 +315,9 @@ class Config extends Secure_Controller 'quantity_decimals' => $this->input->post('quantity_decimals'), 'country_codes' => $this->input->post('country_codes'), 'payment_options_order' => $this->input->post('payment_options_order'), - 'date_or_time_format' => $this->input->post('date_or_time_format') + 'date_or_time_format' => $this->input->post('date_or_time_format'), + 'cash_decimals' => $this->input->post('cash_decimals'), + 'cash_rounding_code' => $this->input->post('cash_rounding_code') ); $result = $this->Appconfig->batch_save($batch_save_data); diff --git a/application/controllers/Customers.php b/application/controllers/Customers.php index 8117831af..ca0b27c4f 100644 --- a/application/controllers/Customers.php +++ b/application/controllers/Customers.php @@ -63,13 +63,15 @@ class Customers extends Persons */ public function view($customer_id = -1) { + $customer_sales_tax_support = $this->config->item('customer_sales_tax_support'); + $info = $this->Customer->get_info($customer_id); foreach(get_object_vars($info) as $property => $value) { $info->$property = $this->xss_clean($value); } $data['person_info'] = $info; - + $data['sales_tax_code_label'] = $info->sales_tax_code . ' ' .$this->Tax->get_info($info->sales_tax_code)->tax_code_name; $data['total'] = $this->xss_clean($this->Customer->get_totals($customer_id)->total); $packages = array('' => $this->lang->line('items_none')); foreach($this->Customer_rewards->get_all()->result_array() as $row) @@ -79,6 +81,15 @@ class Customers extends Persons $data['packages'] = $packages; $data['selected_package'] = $info->package_id; + if ($customer_sales_tax_support == '1') + { + $data['customer_sales_tax_enabled'] = TRUE; + } + else + { + $data['customer_sales_tax_enabled'] = FALSE; + } + $this->load->view("customers/form", $data); } @@ -101,12 +112,14 @@ class Customers extends Persons 'country' => $this->input->post('country'), 'comments' => $this->input->post('comments') ); + $customer_data = array( 'account_number' => $this->input->post('account_number') == '' ? NULL : $this->input->post('account_number'), 'company_name' => $this->input->post('company_name') == '' ? NULL : $this->input->post('company_name'), 'discount_percent' => $this->input->post('discount_percent') == '' ? 0.00 : $this->input->post('discount_percent'), 'package_id' => $this->input->post('package_id') == '' ? NULL : $this->input->post('package_id'), - 'taxable' => $this->input->post('taxable') != NULL + 'taxable' => $this->input->post('taxable') != NULL, + 'sales_tax_code' => $this->input->post('sales_tax_code') ); if($this->Customer->save_customer($person_data, $customer_data, $customer_id)) diff --git a/application/controllers/Items.php b/application/controllers/Items.php index f4efe7220..ee1b73ade 100644 --- a/application/controllers/Items.php +++ b/application/controllers/Items.php @@ -197,6 +197,7 @@ class Items extends Secure_Controller $item_info->reorder_level = 0; $item_info->item_type = '0'; // standard $item_info->stock_type = '0'; // stock + $item_info->tax_category_id = 0; } $data['item_info'] = $item_info; @@ -209,6 +210,25 @@ class Items extends Secure_Controller $data['suppliers'] = $suppliers; $data['selected_supplier'] = $item_info->supplier_id; + $customer_sales_tax_support = $this->config->item('customer_sales_tax_support'); + if($customer_sales_tax_support == '1') + { + $data['customer_sales_tax_enabled'] = TRUE; + $tax_categories = array(); + foreach($this->Tax->get_all_tax_categories()->result_array() as $row) + { + $tax_categories[$this->xss_clean($row['tax_category_id'])] = $this->xss_clean($row['tax_category']); + } + $data['tax_categories'] = $tax_categories; + $data['selected_tax_category'] = $item_info->tax_category_id; + } + else + { + $data['customer_sales_tax_enabled'] = FALSE; + $data['tax_categories'] = array(); + $data['selected_tax_category'] = ''; + } + $data['logo_exists'] = $item_info->pic_filename != ''; $ext = pathinfo($item_info->pic_filename, PATHINFO_EXTENSION); if($ext == '') @@ -371,6 +391,16 @@ class Items extends Secure_Controller 'custom9' => $this->input->post('custom9') == NULL ? '' : $this->input->post('custom9'), 'custom10' => $this->input->post('custom10') == NULL ? '' : $this->input->post('custom10') ); + + $x = $this->input->post('tax_category_id'); + if(!isset($x)) + { + $item['tax_category_id'] = ''; + } + else + { + $item['tax_category_id'] = $this->input->post('selected_tax_category'); + } if(!empty($upload_data['orig_name'])) { diff --git a/application/controllers/Login.php b/application/controllers/Login.php index a463ac674..46a5e4764 100644 --- a/application/controllers/Login.php +++ b/application/controllers/Login.php @@ -34,6 +34,7 @@ class Login extends CI_Controller $this->tracking_lib->track_event('Stats', 'Language', $this->config->item('language')); $this->tracking_lib->track_event('Stats', 'Timezone', $this->config->item('timezone')); $this->tracking_lib->track_event('Stats', 'Currency', $this->config->item('currency_symbol')); + $this->tracking_lib->track_event('Stats', 'Customer Sales Tax Support', $this->config->item('customer_sales_tax_support')); $this->tracking_lib->track_event('Stats', 'Tax Included', $this->config->item('tax_included')); $this->tracking_lib->track_event('Stats', 'Thousands Separator', $this->config->item('thousands_separator')); $this->tracking_lib->track_event('Stats', 'Currency Decimals', $this->config->item('currency_decimals')); @@ -71,7 +72,7 @@ class Login extends CI_Controller private function _security_check($username, $password) { - return preg_match('~\b(Copyright|(c)|©|All rights reserved|Developed|Crafted|Implemented|Made|Powered|Code|Design|unblockUI|blockUI|blockOverlay|hide|opacity)\b~i', file_get_contents(APPPATH . 'views/partial/footer.php')); + return preg_match('~\b(Copyright|(c)|©|All rights reserved|Developed|Crafted|Implemented|Made|Powered|Code|Design|unblockUI|blockUI|blockOverlay|hide|opacity)\b~i', file_get_contents(APPPATH . 'views/partial/footer.php')); } } ?> diff --git a/application/controllers/Receivings.php b/application/controllers/Receivings.php index 2dda154f2..d0eb93381 100644 --- a/application/controllers/Receivings.php +++ b/application/controllers/Receivings.php @@ -27,6 +27,18 @@ class Receivings extends Secure_Controller echo json_encode($suggestions); } + public function stock_item_search() + { + $suggestions = $this->Item->get_stock_search_suggestions($this->input->get('term'), array('search_custom' => FALSE, 'is_deleted' => FALSE), TRUE); + $suggestions = array_merge($suggestions, $this->Item_kit->get_search_suggestions($this->input->get('term'))); + + $suggestions = $this->xss_clean($suggestions); + + echo json_encode($suggestions); + } + + + public function select_supplier() { $supplier_id = $this->input->post('supplier'); diff --git a/application/controllers/Sales.php b/application/controllers/Sales.php index 674ecf4c8..f5215d2c7 100644 --- a/application/controllers/Sales.php +++ b/application/controllers/Sales.php @@ -9,6 +9,7 @@ class Sales extends Secure_Controller parent::__construct('sales'); $this->load->library('sale_lib'); + $this->load->library('tax_lib'); $this->load->library('barcode_lib'); $this->load->library('email_lib'); $this->load->library('token_lib'); @@ -172,6 +173,12 @@ class Sales extends Secure_Controller $this->sale_lib->set_invoice_number_enabled($this->input->post('sales_invoice_number_enabled')); } + public function set_payment_type() + { + $this->sale_lib->set_payment_type($this->input->post('selected_payment_type')); + $this->_reload(); + } + public function set_print_after_sale() { $this->sale_lib->set_print_after_sale($this->input->post('sales_print_after_sale')); @@ -410,6 +417,7 @@ class Sales extends Secure_Controller $this->sale_lib->clear_rewards_remainder(); $this->sale_lib->delete_payment($this->lang->line('sales_rewards')); $this->sale_lib->clear_invoice_number(); + $this->sale_lib->clear_quote_number(); $this->sale_lib->remove_customer(); $this->_reload(); @@ -425,20 +433,11 @@ class Sales extends Secure_Controller $data = array(); $data['dinner_table'] = $this->sale_lib->get_dinner_table(); $data['cart'] = $this->sale_lib->get_cart(); - $data['subtotal'] = $this->sale_lib->get_subtotal(); - $data['discounted_subtotal'] = $this->sale_lib->get_subtotal(TRUE); - $data['tax_exclusive_subtotal'] = $this->sale_lib->get_subtotal(TRUE, TRUE); - $data['taxes'] = $this->sale_lib->get_taxes(); - $data['total'] = $this->sale_lib->get_total(); - $data['discount'] = $this->sale_lib->get_discount(); $data['receipt_title'] = $this->lang->line('sales_receipt'); $data['transaction_time'] = date($this->config->item('dateformat') . ' ' . $this->config->item('timeformat')); $data['transaction_date'] = date($this->config->item('dateformat')); $data['show_stock_locations'] = $this->Stock_location->show_locations('sales'); $data['comments'] = $this->sale_lib->get_comment(); - $data['payments'] = $this->sale_lib->get_payments(); - $data['amount_change'] = $this->sale_lib->get_amount_due() * -1; - $data['amount_due'] = $this->sale_lib->get_amount_due(); $employee_id = $this->Employee->get_logged_in_employee_info()->person_id; $employee_info = $this->Employee->get_info($employee_id); $data['employee'] = $employee_info->first_name . ' ' . $employee_info->last_name[0]; @@ -453,12 +452,44 @@ class Sales extends Secure_Controller $data['print_after_sale'] = $this->sale_lib->is_print_after_sale(); $data['email_receipt'] = $this->sale_lib->get_email_receipt(); $customer_id = $this->sale_lib->get_customer(); + $data["invoice_number"] = $this->sale_lib->get_invoice_number(); + $data["quote_number"] = $this->sale_lib->get_quote_number(); $customer_info = $this->_load_customer_data($customer_id, $data); + $data['taxes'] = $this->sale_lib->get_taxes(); + $data['discount'] = $this->sale_lib->get_discount(); + $data['payments'] = $this->sale_lib->get_payments(); + + // Returns 'subtotal', 'total', 'cash_total', 'payment_total', 'amount_due', 'cash_amount_due', 'payments_cover_total' + $totals = $this->sale_lib->get_totals(); + $data['subtotal'] = $totals['discounted_subtotal']; + $data['cash_total'] = $totals['cash_total']; + $data['cash_amount_due'] = $totals['cash_amount_due']; + $data['non_cash_total'] =$totals['total']; + $data['non_cash_amount_due'] =$totals['amount_due']; + $data['payments_total'] = $totals['payment_total']; + $data['payments_cover_total'] = $totals['payments_cover_total']; + $data['cash_rounding'] = $this->session->userdata('cash_rounding'); + + $data['discounted_subtotal'] = $totals['discounted_subtotal']; + $data['tax_exclusive_subtotal'] = $totals['tax_exclusive_subtotal']; + + if ($data['cash_rounding']) + { + $data['total'] = $totals['cash_total']; + $data['amount_due'] = $totals['cash_amount_due']; + } + else + { + $data['total'] = $totals['total']; + $data['amount_due'] = $totals['amount_due']; + } + $data['amount_change'] = $data['amount_due'] * -1; + if ($this->sale_lib->is_invoice_mode() || $data['invoice_number_enabled'] == true) { // generate final invoice number (if using the invoice in sales by receipt mode then the invoice number can be manually entered or altered in some way - if ($this->sale_lib->is_sale_by_receipt_mode()) + if($this->sale_lib->is_sale_by_receipt_mode()) { $this->sale_lib->set_invoice_number($this->input->post('invoice_number'), $keep_custom = TRUE); $invoice_format = $this->sale_lib->get_invoice_number(); @@ -473,8 +504,6 @@ class Sales extends Secure_Controller } $invoice_number = $this->token_lib->render($invoice_format); - $quote_number = null; - // TODO If duplicate invoice then determine the number of employees and repeat until until success or tried the number of employees (if QSEQ was used). if($this->Sale->check_invoice_number_exists($invoice_number)) { @@ -484,10 +513,10 @@ class Sales extends Secure_Controller else { $data['invoice_number'] = $invoice_number; - $data['quote_number'] = $quote_number; + $data['sale_status'] = '0'; // Complete // Save the data to the sales table - $data['sale_id_num'] = $this->Sale->save($data['cart'], $customer_id, $employee_id, $data['comments'], $invoice_number, $data['payments'], $data['dinner_table']); + $data['sale_id_num'] = $this->Sale->save($data['sale_status'], $data['cart'], $customer_id, $employee_id, $data['comments'], $invoice_number, $data["quote_number"], $data['payments'], $data['dinner_table'], $data['taxes']); $data['sale_id'] = 'POS ' . $data['sale_id_num']; // Resort and filter cart lines for printing @@ -509,7 +538,9 @@ class Sales extends Secure_Controller } elseif($this->sale_lib->is_quote_mode()) { + $invoice_number = null; $quote_number = $this->sale_lib->get_quote_number(); + if($quote_number == null) { // generate quote number @@ -517,8 +548,6 @@ class Sales extends Secure_Controller $quote_number = $this->token_lib->render($quote_format); } - $invoice_number = null; - // TODO If duplicate quote then determine the number of employees and repeat until until success or tried the number of employees (if QSEQ was used). if($this->Sale->check_quote_number_exists($quote_number)) { @@ -529,13 +558,19 @@ class Sales extends Secure_Controller { $data['invoice_number'] = $invoice_number; $data['quote_number'] = $quote_number; + $data['sale_status'] = '1'; // Suspended + + $data['sale_id_num'] = $this->Sale->save($data['sale_status'], $data['cart'], $customer_id, $employee_id, $data['comments'], $invoice_number, $quote_number, $data['payments'], $data['dinner_table'], $data['taxes']); $data['cart'] = $this->sale_lib->sort_and_filter_cart($data['cart']); $data = $this->xss_clean($data); + $data['barcode'] = NULL; - $this->suspend_quote($quote_number); + +// $this->suspend_quote($quote_number); + $this->load->view('sales/quote', $data); $this->sale_lib->clear_mode(); $this->sale_lib->clear_all(); @@ -544,7 +579,9 @@ class Sales extends Secure_Controller else { // Save the data to the sales table - $data['sale_id_num'] = $this->Sale->save($data['cart'], $customer_id, $employee_id, $data['comments'], null, $data['payments'], $data['dinner_table']); + $data['sale_status'] = '0'; // Complete + $data['sale_id_num'] = $this->Sale->save($data['sale_status'], $data['cart'], $customer_id, $employee_id, $data['comments'], null, null, $data['payments'], $data['dinner_table'], $data['taxes']); + $data['sale_id'] = 'POS ' . $data['sale_id_num']; $data['cart'] = $this->sale_lib->sort_and_filter_cart($data['cart']); @@ -720,7 +757,7 @@ class Sales extends Secure_Controller { $text = $this->_substitute_variable($text, '$YCO', $this->Sale, 'get_invoice_number_for_year'); $text = $this->_substitute_variable($text, '$CO', $this->Sale, 'get_invoice_count'); - $text = $this->_substitute_variable($text, '$SCO', $this->Sale_suspended, 'get_invoice_count'); + $text = $this->_substitute_variable($text, '$SCO', $this->Sale, 'get_suspended_invoice_count'); $text = strftime($text); $text = $this->_substitute_customer($text, $customer_info); @@ -801,23 +838,53 @@ class Sales extends Secure_Controller private function _load_sale_data($sale_id) { $this->sale_lib->clear_all(); + $this->sale_lib->reset_cash_flags(); $sale_info = $this->Sale->get_info($sale_id)->row_array(); $this->sale_lib->copy_entire_sale($sale_id); $data = array(); $data['cart'] = $this->sale_lib->get_cart(); $data['payments'] = $this->sale_lib->get_payments(); - $data['subtotal'] = $this->sale_lib->get_subtotal(); - $data['discounted_subtotal'] = $this->sale_lib->get_subtotal(TRUE); - $data['tax_exclusive_subtotal'] = $this->sale_lib->get_subtotal(TRUE, TRUE); + $data['selected_payment_type'] = $this->sale_lib->get_payment_type(); + +// $data['subtotal'] = $this->sale_lib->get_subtotal(); +// $data['discounted_subtotal'] = $this->sale_lib->get_subtotal(TRUE); +// $data['tax_exclusive_subtotal'] = $this->sale_lib->get_subtotal(TRUE, TRUE); +// $data['amount_due'] = $this->sale_lib->get_amount_due(); +// $data['amount_change'] = $this->sale_lib->get_amount_due() * -1; +// $data['total'] = $this->sale_lib->get_total(); + $data['taxes'] = $this->sale_lib->get_taxes(); - $data['total'] = $this->sale_lib->get_total(); $data['discount'] = $this->sale_lib->get_discount(); $data['receipt_title'] = $this->lang->line('sales_receipt'); $data['transaction_time'] = date($this->config->item('dateformat') . ' ' . $this->config->item('timeformat'), strtotime($sale_info['sale_time'])); $data['transaction_date'] = date($this->config->item('dateformat'), strtotime($sale_info['sale_time'])); $data['show_stock_locations'] = $this->Stock_location->show_locations('sales'); - $data['amount_change'] = $this->sale_lib->get_amount_due() * -1; - $data['amount_due'] = $this->sale_lib->get_amount_due(); + + + // Returns 'subtotal', 'total', 'cash_total', 'payment_total', 'amount_due', 'cash_amount_due', 'payments_cover_total' + $totals = $this->sale_lib->get_totals(); + $data['subtotal'] = $totals['subtotal']; + $data['discounted_subtotal'] = $totals['discounted_subtotal']; + $data['tax_exclusive_subtotal'] = $totals['tax_exclusive_subtotal']; + $data['cash_total'] = $totals['cash_total']; + $data['cash_amount_due'] = $totals['cash_amount_due']; + $data['non_cash_total'] = $totals['total']; + $data['non_cash_amount_due'] = $totals['amount_due']; + $data['payments_total'] = $totals['payment_total']; + $data['payments_cover_total'] = $totals['payments_cover_total']; + + if ($this->session->userdata('cash_rounding')) + { + $data['total'] = $totals['cash_total']; + $data['amount_due'] = $totals['cash_amount_due']; + } + else + { + $data['total'] = $totals['total']; + $data['amount_due'] = $totals['amount_due']; + } + $data['amount_change'] = $data['amount_due'] * -1; + $employee_info = $this->Employee->get_info($this->sale_lib->get_employee()); $data['employee'] = $employee_info->first_name . ' ' . $employee_info->last_name[0]; $this->_load_customer_data($this->sale_lib->get_customer(), $data); @@ -826,6 +893,8 @@ class Sales extends Secure_Controller $data['sale_id'] = 'POS ' . $sale_id; $data['comments'] = $sale_info['comment']; $data['invoice_number'] = $sale_info['invoice_number']; + $data['quote_number'] = $sale_info['quote_number']; + $data['sale_status'] = $sale_info['sale_status']; $data['company_info'] = implode("\n", array( $this->config->item('address'), $this->config->item('phone'), @@ -868,16 +937,38 @@ class Sales extends Secure_Controller $data['selected_table'] = $this->sale_lib->get_dinner_table(); $data['stock_locations'] = $this->Stock_location->get_allowed_locations('sales'); $data['stock_location'] = $this->sale_lib->get_sale_location(); - $data['subtotal'] = $this->sale_lib->get_subtotal(TRUE); $data['tax_exclusive_subtotal'] = $this->sale_lib->get_subtotal(TRUE, TRUE); + $data['taxes'] = $this->sale_lib->get_taxes(); $data['discount'] = $this->sale_lib->get_discount(); - $data['total'] = $this->sale_lib->get_total(); + $data['payments'] = $this->sale_lib->get_payments(); + + // Returns 'subtotal', 'total', 'cash_total', 'payment_total', 'amount_due', 'cash_amount_due', 'payments_cover_total' + $totals = $this->sale_lib->get_totals(); + $data['subtotal'] = $totals['discounted_subtotal']; + $data['cash_total'] = $totals['cash_total']; + $data['cash_amount_due'] = $totals['cash_amount_due']; + $data['non_cash_total'] =$totals['total']; + $data['non_cash_amount_due'] =$totals['amount_due']; + $data['payments_total'] = $totals['payment_total']; + $data['payments_cover_total'] = $totals['payments_cover_total']; + $data['cash_rounding'] = $this->session->userdata('cash_rounding'); + + if ($data['cash_rounding']) + { + $data['total'] = $totals['cash_total']; + $data['amount_due'] = $totals['cash_amount_due']; + } + else + { + $data['total'] = $totals['total']; + $data['amount_due'] = $totals['amount_due']; + } + $data['amount_change'] = $data['amount_due'] * -1; + $data['comment'] = $this->sale_lib->get_comment(); $data['email_receipt'] = $this->sale_lib->get_email_receipt(); - $data['payments_total'] = $this->sale_lib->get_payments_total(); - $data['amount_due'] = $this->sale_lib->get_amount_due(); - $data['payments'] = $this->sale_lib->get_payments(); + $data['selected_payment_type'] = $this->sale_lib->get_payment_type(); if($customer_info && $this->config->item('customer_reward_enable') == TRUE) $data['payment_options'] = $this->Sale->get_payment_options(TRUE,TRUE); else @@ -898,7 +989,6 @@ class Sales extends Secure_Controller $data['invoice_number_enabled'] = $this->sale_lib->is_invoice_mode(); $data['print_after_sale'] = $this->sale_lib->is_print_after_sale(); - $data['payments_cover_total'] = $this->sale_lib->is_payment_covering_total(); $data['quote_or_invoice_mode'] = $data['mode'] == 'sale_invoice' || $data['mode'] == 'sale_quote'; $data['sales_or_return_mode'] = $data['mode'] == 'sale' || $data['mode'] == 'return'; if($this->sale_lib->get_mode() == 'sale_invoice') @@ -1050,7 +1140,7 @@ class Sales extends Secure_Controller { $suspended_id = $this->sale_lib->get_suspended_id(); $this->sale_lib->clear_all(); - $this->Sale_suspended->delete($suspended_id); + $this->Sale->delete_suspended_sale($suspended_id); $this->_reload(); } @@ -1065,10 +1155,11 @@ class Sales extends Secure_Controller $invoice_number = $this->_is_custom_invoice_number($customer_info) ? $this->sale_lib->get_invoice_number() : NULL; $quote_number = $this->sale_lib->get_quote_number(); $comment = $this->sale_lib->get_comment(); + $sale_status = '1'; - //SAVE sale to database $data = array(); - if ($this->Sale_suspended->save($cart, $customer_id, $employee_id, $comment, $invoice_number, $quote_number, $payments, $dinner_table) == '-1') + $sales_taxes = array(); + if ($this->Sale->save($sale_status, $cart, $customer_id, $employee_id, $comment, $invoice_number, $quote_number, $payments, $dinner_table, $sales_taxes) == '-1') { $data['error'] = $this->lang->line('sales_unsuccessfully_suspended_sale'); } @@ -1088,14 +1179,14 @@ class Sales extends Secure_Controller $employee_id = $this->Employee->get_logged_in_employee_info()->person_id; $customer_id = $this->sale_lib->get_customer(); $customer_info = $this->Customer->get_info($customer_id); + $dinner_table = $this->sale_lib->get_dinner_table(); $invoice_number = $this->_is_custom_invoice_number($customer_info) ? $this->sale_lib->get_invoice_number() : NULL; $comment = $this->sale_lib->get_comment(); + $sale_status = '2'; // Suspend - //SAVE sale to database $data = array(); - $suspended_id = $this->Sale_suspended->save($cart, $customer_id, $employee_id, $comment, $invoice_number, $quote_number, $payments); - $this->sale_lib->set_suspended_id($suspended_id); - if ($suspended_id == '-1') + $sales_taxes = array(); + if ($this->Sale->save($sale_status, $cart, $customer_id, $employee_id, $comment, $invoice_number, $quote_number, $payments, $dinner_table, $sales_taxes) == '-1') { $data['error'] = $this->lang->line('sales_unsuccessfully_suspended_sale'); } @@ -1107,17 +1198,34 @@ class Sales extends Secure_Controller public function suspended() { + $customer_id = $this->sale_lib->get_customer(); $data = array(); - $data['suspended_sales'] = $this->xss_clean($this->Sale_suspended->get_all()->result_array()); + $data['suspended_sales'] = $this->xss_clean($this->Sale->get_all_suspended($customer_id)); + $data['dinner_table_enable'] = $this->config->item('dinner_table_enable'); $this->load->view('sales/suspended', $data); } + /* + * We will eventually drop the current set of "suspended" tables since suspended sales + * are now stored in the sales tables with a sale_status value of suspended. + */ public function unsuspend() { $sale_id = $this->input->post('suspended_sale_id'); $this->sale_lib->clear_all(); - $this->sale_lib->copy_entire_suspended_sale($sale_id); - $this->Sale_suspended->delete($sale_id); + + if($sale_id > 0) { + $this->sale_lib->copy_entire_sale($sale_id); + $this->Sale->delete_suspended_sale($sale_id); + } + else + { + // This will unsuspended older suspended sales + $sale_id = $sale_id * -1; + $this->sale_lib->copy_entire_suspended_tables_sale($sale_id); + $this->Sale_suspended->delete($sale_id); + } + $this->_reload(); } diff --git a/application/controllers/Taxes.php b/application/controllers/Taxes.php new file mode 100644 index 000000000..ea9183990 --- /dev/null +++ b/application/controllers/Taxes.php @@ -0,0 +1,227 @@ +load->model('enums/Rounding_code'); + + } + + public function index() + { + $data['table_headers'] = $this->xss_clean(get_taxes_manage_table_headers()); + + $this->load->view('taxes/manage', $data); + } + + /* + Returns tax_codes table data rows. This will be called with AJAX. + */ + public function search() + { + $search = $this->input->get('search'); + $limit = $this->input->get('limit'); + $offset = $this->input->get('offset'); + $sort = $this->input->get('sort'); + $order = $this->input->get('order'); + + $tax_codes = $this->Tax->search($search, $limit, $offset, $sort, $order); + + $total_rows = $this->Tax->get_found_rows($search); + + $data_rows = array(); + foreach($tax_codes->result() as $tax_code_row) + { + $data_rows[] = get_tax_data_row($tax_code_row, $this); + } + + $data_rows = $this->xss_clean($data_rows); + + echo json_encode(array('total' => $total_rows, 'rows' => $data_rows)); + } + + /* + Gives search suggestions based on what is being searched for + */ + public function suggest_search() + { + $suggestions = $this->xss_clean($this->Tax->get_search_suggestions($this->input->post('term'))); + + echo json_encode($suggestions); + } + + /* + Provides list of tax categories to select from + */ + public function suggest_tax_categories() + { + $suggestions = $this->xss_clean($this->Tax->get_tax_category_suggestions($this->input->post('term'))); + + echo json_encode($suggestions); + } + + + public function get_row($row_id) + { + $data_row = $this->xss_clean(get_tax_codes_data_row($this->Tax->get_info($row_id), $this)); + + echo json_encode($data_row); + } + + public function view($tax_code = -1) + { + $tax_code_info = $this->Tax->get_info($tax_code); + + $default_tax_category_id = 0; // Tax category id is always the default tax category + $default_tax_category = $this->Tax->get_tax_category($default_tax_category_id); + + $tax_rate_info = $this->Tax->get_rate_info($tax_code, $default_tax_category_id); + + $data['rounding_options'] = Rounding_code::get_rounding_options(); + + if ($tax_code == -1) + { + $data['tax_code'] = ''; + $data['tax_code_name'] = ''; + $data['tax_code_type'] = '0'; + $data['city'] = ''; + $data['state'] = ''; + $data['tax_rate'] = '0.0000'; + $data['rate_tax_code'] = ''; + $data['rate_tax_category_id'] = '0'; + $data['tax_category'] = ''; + $data['add_tax_category'] = ''; + $data['rounding_code'] = '0'; + } + else + { + $data['tax_code'] = $tax_code; + $data['tax_code_name'] = $tax_code_info->tax_code_name; + $data['tax_code_type'] = $tax_code_info->tax_code_type; + $data['city'] = $tax_code_info->city; + $data['state'] = $tax_code_info->state; + $data['rate_tax_code'] = $tax_code_info->rate_tax_code; + $data['rate_tax_category_id'] = $tax_code_info->rate_tax_category_id; + $data['tax_category'] = $tax_code_info->tax_category; + $data['add_tax_category'] = ''; + $data['tax_rate'] = $tax_rate_info->tax_rate; + $data['rounding_code'] = $tax_rate_info->rounding_code; + } + + $data = $this->xss_clean($data); + + $tax_code_rates = array(); + foreach($this->Tax->get_tax_code_rate_exceptions($tax_code) as $tax_code_rate) + { + $tax_code_row = array(); + $tax_code_row['rate_tax_category_id'] = $this->xss_clean($tax_code_rate['rate_tax_category_id']); + $tax_code_row['tax_category'] = $this->xss_clean($tax_code_rate['tax_category']); + $tax_code_row['tax_rate'] = $this->xss_clean($tax_code_rate['tax_rate']); + $tax_code_row['rounding_code'] = $this->xss_clean($tax_code_rate['rounding_code']); + + $tax_code_rates[] = $tax_code_row; + } + + $data['tax_code_rates'] = $tax_code_rates; + + $this->load->view("taxes/form", $data); + } + + public static function get_html_rounding_options() + { + return Rounding_code::get_html_rounding_options(); + } + + public function save($tax_code = -1) + { + $entered_tax_code = $this->xss_clean($this->input->post('tax_code')); + $tax_code_data = array( + 'tax_code' => $this->input->post('tax_code'), + 'tax_code_name' => $this->input->post('tax_code_name'), + 'tax_code_type' => $this->input->post('tax_code_type'), + 'city' => $this->input->post('city'), + 'state' => $this->input->post('state')); + + $tax_rate_data = array( + 'rate_tax_code' => $this->input->post('tax_code'), + 'rate_tax_category_id' => 0, + 'tax_rate' => parse_decimals($this->input->post('tax_rate')), + 'rounding_code' => parse_decimals($this->input->post('rounding_code')) + ); + + if($this->Tax->save($tax_code_data, $tax_rate_data, $tax_code)) + { + $tax_code_rate_exceptions = array(); + if(!empty($this->input->post('exception_tax_rate'))) + { + foreach($this->input->post('exception_tax_rate') as $tax_category_id => $exception_tax_rate) + { + $exception_rounding_code = $this->input->post('exception_rounding_code[' . $tax_category_id . ']'); + $tax_code_rate_exceptions[] = array( + 'rate_tax_code' => $entered_tax_code, + 'rate_tax_category_id' => $tax_category_id, + 'tax_rate' => $exception_tax_rate, + 'rounding_code' => $exception_rounding_code + ); + } + } + + if (!empty($tax_code_rate_exceptions)) + { + $success = $this->Tax->save_tax_rate_exceptions($tax_code_rate_exceptions, $entered_tax_code); + } + + $tax_code_data = $this->xss_clean($tax_code_data); + + //New tax_code record + if($tax_code == -1) + { + echo json_encode(array('success' => TRUE, 'message' => $this->lang->line('taxes_tax_code_successfully_added') . ' ' . $entered_tax_code)); + } + else //Existing tax_code + { + echo json_encode(array('success' => TRUE, 'message' => $this->lang->line('taxes_tax_code_successful_updated') . ' ' . $entered_tax_code)); + } + } + else //failure + { + $tax_code_data = $this->xss_clean($tax_code_data); + + echo json_encode(array('success' => FALSE, 'message' => $this->lang->line('taxes_tax_code_error_adding_updating') . ' ' . + $entered_tax_code)); + } + } + + public function delete() + { + $tax_codes_to_delete = $this->xss_clean($this->input->post('ids')); + + if($this->Tax->delete_list($tax_codes_to_delete)) + { + echo json_encode(array('success' => TRUE, 'message' => $this->lang->line('taxes_tax_code_successful_deleted'))); + } else + { + echo json_encode(array('success' => FALSE, 'message' => $this->lang->line('taxes_tax_code_cannot_be_deleted'))); + } + } + + public function suggest_sales_tax_codes() + { + $suggestions = $this->xss_clean($this->Tax->get_sales_tax_codes_search_suggestions($this->input->post_get('term'))); + + echo json_encode($suggestions); + } + + public function get_rounding_code_name($rounding_code) + { + return Rounding_code::get_rounding_code_name($rounding_code); + } + + +} +?> \ No newline at end of file diff --git a/application/helpers/table_helper.php b/application/helpers/table_helper.php index 64f8c6d3f..954839598 100644 --- a/application/helpers/table_helper.php +++ b/application/helpers/table_helper.php @@ -327,6 +327,23 @@ function get_giftcards_manage_table_headers() return transform_headers($headers); } +function get_taxes_manage_table_headers() +{ + $CI =& get_instance(); + + $headers = array( + array('tax_code' => $CI->lang->line('taxes_tax_code')), + array('tax_code_name' => $CI->lang->line('taxes_tax_code_name')), + array('tax_code_type_name' => $CI->lang->line('taxes_tax_code_type')), + array('tax_rate' => $CI->lang->line('taxes_tax_rate')), + array('rounding_code_name' => $CI->lang->line('taxes_rounding_code')), + array('city' => $CI->lang->line('common_city')), + array('state' => $CI->lang->line('common_state')) + ); + + return transform_headers($headers); +} + function get_giftcard_data_row($giftcard, $controller) { $CI =& get_instance(); @@ -343,6 +360,26 @@ function get_giftcard_data_row($giftcard, $controller) )); } +function get_tax_data_row($tax_code_row, $controller) +{ + $CI =& get_instance(); + $controller_name=strtolower(get_class($CI)); + + return array ( + 'tax_code' => $tax_code_row->tax_code, + 'tax_code_name' => $tax_code_row->tax_code_name, + 'tax_code_type' => $tax_code_row->tax_code_type, + 'tax_rate' => $tax_code_row->tax_rate, + 'rounding_code' =>$tax_code_row->rounding_code, + 'tax_code_type_name' => $CI->Tax->get_tax_code_type_name($tax_code_row->tax_code_type), + 'rounding_code_name' => $CI->get_rounding_code_name($tax_code_row->rounding_code), + 'city' => $tax_code_row->city, + 'state' => $tax_code_row->state, + 'edit' => anchor($controller_name."/view/$tax_code_row->tax_code", '', + array('class'=>'modal-dlg', 'data-btn-submit' => $CI->lang->line('common_submit'), 'title'=>$CI->lang->line($controller_name.'_update')) + )); +} + function get_item_kits_manage_table_headers() { $CI =& get_instance(); diff --git a/application/language/en/config_lang.php b/application/language/en/config_lang.php index 6b525963f..f057e3fd9 100644 --- a/application/language/en/config_lang.php +++ b/application/language/en/config_lang.php @@ -29,6 +29,9 @@ $lang["config_barcode_type"] = "Barcode Type"; $lang["config_barcode_width"] = "Width (px)"; $lang["config_bottom"] = "Bottom"; $lang["config_center"] = "Center"; +$lang["config_cash_decimals"] = "Cash Decimals"; +$lang["config_cash_decimals_tooltip"] = "If cash decimals and currency decimals are the same then no cash rounding will take place."; +$lang["config_cash_rounding"] = "Cash Rounding"; $lang["config_comma"] = "comma"; $lang["config_company"] = "Company Name"; $lang["config_company_change_image"] = "Change Image"; @@ -56,6 +59,7 @@ $lang["config_customer_reward_duplicate"] = "Please use an unique reward name"; $lang["config_customer_reward_enable"] = "Enable Customer Rewards"; $lang["config_customer_reward_invalid_chars"] = "The reward name can not contain '_'"; $lang["config_customer_reward_required"] = "Reward is a required field"; +$lang["config_customer_sales_tax_support"] = "Customer Sales Tax Support"; $lang["config_date_or_time_format"] = "Date and Time filter"; $lang["config_datetimeformat"] = "Date and Time format"; $lang["config_decimal_point"] = "Decimal Point"; @@ -73,6 +77,7 @@ $lang["config_default_barcode_quality_number"] = "The default barcode quality mu $lang["config_default_barcode_quality_required"] = "The default barcode quality is a required field"; $lang["config_default_barcode_width_number"] = "The default barcode width must be a number"; $lang["config_default_barcode_width_required"] = "The default barcode width is a required field"; +$lang["config_default_origin_tax_code"] = "Default Origin Tax Code"; $lang["config_default_sales_discount"] = "Default Sales Discount %"; $lang["config_default_sales_discount_number"] = "The default sales discount must be a number"; $lang["config_default_sales_discount_required"] = "The default sales discount is a required field"; diff --git a/application/language/en/customers_lang.php b/application/language/en/customers_lang.php index 9e5f3cb9c..4e16b923b 100644 --- a/application/language/en/customers_lang.php +++ b/application/language/en/customers_lang.php @@ -20,6 +20,7 @@ $lang["customers_one_or_multiple"] = "customer(s)"; $lang["customers_successful_adding"] = "You have successfully added customer"; $lang["customers_successful_deleted"] = "You have successfully deleted"; $lang["customers_successful_updating"] = "You have successfully updated customer"; +$lang["customers_tax_code"] = "Tax Code"; $lang["customers_taxable"] = "Taxable"; $lang["customers_total"] = "Total"; $lang["customers_update"] = "Update Customer"; diff --git a/application/language/en/enum_lang.php b/application/language/en/enum_lang.php new file mode 100644 index 000000000..21eb2755b --- /dev/null +++ b/application/language/en/enum_lang.php @@ -0,0 +1,9 @@ +CI =& get_instance(); + $this->CI->load->library('tax_lib'); + $this->CI->load->model('enums/Rounding_code'); + } public function get_line_sequence_options() @@ -298,17 +301,112 @@ class Sale_lib public function get_payments_total() { $subtotal = 0; + $this->reset_cash_flags(); foreach($this->get_payments() as $payments) { $subtotal = bcadd($payments['payment_amount'], $subtotal); + if($this->CI->session->userdata('cash_rounding') && $this->CI->lang->line('sales_cash') != $payments['payment_type']) + { + $this->CI->session->set_userdata('cash_rounding', 0); + } } return $subtotal; } + public function get_cash_rounding() + { + + } + + /* + * Returns 'subtotal', 'total', 'cash_total', 'payment_total', 'amount_due', 'cash_amount_due', 'paid_in_full' + * 'subtotal', 'discounted_subtotal', 'tax_exclusive_subtotal' + */ + public function get_totals() + { + $cash_rounding = $this->CI->session->userdata('cash_rounding'); + + $totals = array(); + + $subtotal = 0; + $discounted_subtotal = 0; + $tax_exclusive_subtotal = 0; + foreach($this->get_cart() as $item) + { + $subtotal = bcadd($subtotal, $this->get_item_total($item['quantity'], $item['price'], $item['discount'], false)); + $discounted_subtotal = bcadd($discounted_subtotal, $this->get_item_total($item['quantity'], $item['price'], $item['discount'], true)); + if($this->CI->config->config['tax_included']) + { + $tax_exclusive_subtotal = bcadd($tax_exclusive_subtotal, $this->get_item_total_tax_exclusive($item['item_id'], $item['quantity'], $item['price'], $item['discount'], true)); + } + } + + $totals['subtotal'] = $subtotal; + $totals['discounted_subtotal'] = $discounted_subtotal; + $totals['tax_exclusive_subtotal'] = $tax_exclusive_subtotal; + + $total = $discounted_subtotal; + if ($this->CI->config->config['tax_included']) + { + $totals['total'] = $total; + } + else + { + foreach($this->get_taxes() as $sales_tax) + { + $total = bcadd($total, $sales_tax['sale_tax_amount']); + } + $totals['total'] = $total; + } + + if($cash_rounding) + { + $cash_total = $this->check_for_cash_rounding($total); + $totals['cash_total'] = $cash_total; + } + else + { + $cash_total = $total; + } + + $totals['cash_total'] = $cash_total; + + $payment_total = $this->get_payments_total(); + $totals['payment_total'] = $payment_total; + + $amount_due = bcsub($total, $payment_total); + $totals['amount_due'] = $amount_due; + + $cash_amount_due = bcsub($cash_total, $payment_total); + $totals['cash_amount_due'] = $cash_amount_due; + + if($cash_rounding) + { + $current_due = $cash_amount_due; + } + else + { + $current_due = $amount_due; + } + + if($this->get_mode() == 'return') + { + $totals['payments_cover_total'] = $current_due >= 0; + } + else + { + $totals['payments_cover_total'] = $current_due <= 0; + } + + return $totals; + } + + // Multiple Payments public function get_amount_due() { + // Payment totals need to be identified first so that we know whether or not there is a non-cash payment involved $payment_total = $this->get_payments_total(); $sales_total = $this->get_total(); $amount_due = bcsub($sales_total, $payment_total); @@ -320,16 +418,14 @@ class Sale_lib public function is_payment_covering_total() { - // 0 decimal -> 1 / 2 = 0.5, 1 decimals -> 0.1 / 2 = 0.05, 2 decimals -> 0.01 / 2 = 0.005 - $threshold = bcpow(10, -$this->CI->config->item('currency_decimals')) / 2; if($this->get_mode() == 'return') { - return ($this->get_amount_due() > -$threshold); + return $this->get_amount_due() >= 0; } else { - return ($this->get_amount_due() < $threshold); + return $this->get_amount_due() <= 0; } } @@ -437,6 +533,16 @@ class Sale_lib $this->CI->session->set_userdata('sales_location', $location); } + public function set_payment_type($payment_type) + { + $this->CI->session->set_userdata('payment_type', $payment_type); + } + + public function get_payment_type() + { + return $this->CI->session->userdata('payment_type'); + } + public function clear_sale_location() { $this->CI->session->unset_userdata('sales_location'); @@ -571,7 +677,8 @@ class Sale_lib 'total' => $total, 'discounted_total' => $discounted_total, 'print_option' => $print_option, - 'stock_type' => $stock_type + 'stock_type' => $stock_type, + 'tax_category_id' => $item_info->tax_category_id ) ); //add to existing array @@ -778,6 +885,31 @@ class Sale_lib $this->empty_cart(); $this->remove_customer(); + foreach($this->CI->Sale->get_sale_items($sale_id)->result() as $row) + { + $this->add_item($row->item_id, $row->quantity_purchased, $row->item_location, $row->discount_percent, $row->item_unit_price, + $row->description, $row->serialnumber, TRUE, $row->print_option, $row->stock_type); + } + + foreach($this->CI->Sale->get_sale_payments($sale_id)->result() as $row) + { + $this->add_payment($row->payment_type, $row->payment_amount); + } + + $suspended_sale_info = $this->CI->Sale->get_info($sale_id)->row(); + $this->set_customer($suspended_sale_info->person_id); + $this->set_comment($suspended_sale_info->comment); + + $this->set_invoice_number($suspended_sale_info->invoice_number); + $this->set_quote_number($suspended_sale_info->quote_number); + $this->set_dinner_table($suspended_sale_info->dinner_table_id); + } + + public function copy_entire_suspended_tables_sale($sale_id) + { + $this->empty_cart(); + $this->remove_customer(); + foreach($this->CI->Sale_suspended->get_sale_items($sale_id)->result() as $row) { $this->add_item($row->item_id, $row->quantity_purchased, $row->item_location, $row->discount_percent, $row->item_unit_price, @@ -810,6 +942,37 @@ class Sale_lib $this->clear_giftcard_remainder(); $this->empty_payments(); $this->remove_customer(); + $this->clear_cash_flags(); + } + + public function clear_cash_flags() + { + $this->CI->session->unset_userdata('cash_rounding'); + $this->CI->session->unset_userdata('cash_mode'); + $this->CI->session->unset_userdata('payment_type'); + } + + public function reset_cash_flags() + { + if($this->CI->lang->line('payment_options_order') != 'cashdebitcredit') + { + $cash_mode = 1; + } + else + { + $cash_mode = 0; + } + $this->CI->session->set_userdata('cash_mode', $cash_mode); + + if($this->CI->config->config['cash_decimals'] < $this->CI->config->config['currency_decimals']) + { + $cash_rounding = 1; + } + else + { + $cash_rounding = 0; + } + $this->CI->session->set_userdata('cash_rounding', $cash_rounding); } public function is_customer_taxable() @@ -821,34 +984,68 @@ class Sale_lib return $customer->taxable or $customer_id == -1; } + /* + * This returns taxes for VAT taxes and for pre 3.1.0 sales taxes. + */ public function get_taxes() { - $taxes = array(); - + $register_mode = $this->CI->config->config['default_register_mode']; + $tax_decimals = $this->CI->config->config['tax_decimals']; + $customer_id = $this->get_customer(); + $customer = $this->CI->Customer->get_info($customer_id); + $sales_taxes = array(); //Do not charge sales tax if we have a customer that is not taxable - if($this->is_customer_taxable()) + if($customer->taxable or $customer_id == -1) { foreach($this->get_cart() as $line => $item) { - $tax_info = $this->CI->Item_taxes->get_info($item['item_id']); + // Start of current VAT tax apply + $tax_info = $this->CI->Item_taxes->get_info($item['item_id']); + $tax_group_sequence = 0; foreach($tax_info as $tax) { - $name = to_tax_decimals($tax['percent']) . '% ' . $tax['name']; - $tax_amount = $this->get_item_tax($item['quantity'], $item['price'], $item['discount'], $tax['percent']); + // This computes tax for each line item and adds it to the tax type total + $tax_group = (float)$tax['percent'] . '% ' . $tax['name']; + $tax_type = Tax_lib::TAX_TYPE_VAT; + $tax_basis = $this->get_item_total($item['quantity'], $item['price'], $item['discount'], TRUE); + $tax_amount = 0; - if(!isset($taxes[$name])) + if($this->CI->config->config['tax_included']) { - $taxes[$name] = 0; + $tax_amount = $this->get_item_tax($item['quantity'], $item['price'], $item['discount'], $tax['percent']); + } + elseif($this->CI->config->config['customer_sales_tax_support'] == '0') + { + $tax_amount = $this->CI->tax_lib->get_sales_tax_for_amount($tax_basis, $tax['percent'], '0', $tax_decimals); } - $precision = $this->CI->config->item('currency_decimals'); - $taxes[$name] = bcadd($taxes[$name], round($tax_amount, $precision, PHP_ROUND_HALF_EVEN)); + if($tax_amount <> 0) + { + $this->CI->tax_lib->update_sales_taxes($sales_taxes, $tax_type, $tax_group, $tax['percent'], $tax_basis, $tax_amount, $tax_group_sequence, '0', -1); + $tax_group_sequence += 1; + } } + + $tax_category = ''; + $tax_rate = ''; + $rounding_code = Rounding_code::HALF_UP; + $tax_group_sequence = 0; + $tax_code = ''; + + if($this->CI->config->config['customer_sales_tax_support'] == '1') + { + // Now calculate what the sales taxes should be (storing them in the $sales_taxes array + $this->CI->tax_lib->apply_sales_tax($item, $customer->city, $customer->state, $customer->sales_tax_code, $register_mode, 0, $sales_taxes, $tax_category, $tax_rate, $rounding_code, $tax_group_sequence, $tax_code); + } + } + + $this->CI->tax_lib->round_sales_taxes($sales_taxes); + } - return $taxes; + return $sales_taxes; } public function apply_customer_discount($discount_percent) @@ -944,7 +1141,7 @@ class Sale_lib return bcmul($price, $tax_fraction); } - public function calculate_subtotal($include_discount = FALSE, $exclude_tax = FALSE) + public function calculate_subtotal($include_discount = FALSE, $exclude_tax = FALSE) { $subtotal = 0; foreach($this->get_cart() as $item) @@ -964,22 +1161,67 @@ class Sale_lib public function get_total() { - $total = $this->calculate_subtotal(TRUE); - if(!$this->CI->config->config['tax_included']) + $total = $this->calculate_subtotal(TRUE); + + $cash_rounding = $this->CI->session->userdata('cash_rounding'); + + foreach($this->get_taxes() as $sales_tax) { - foreach($this->get_taxes() as $tax) - { - $total = bcadd($total, $tax); - } + $total = bcadd($total, $sales_tax['sale_tax_amount']); } + if($cash_rounding) + { + $rounded_total = $this->check_for_cash_rounding($total); + return $rounded_total; + } return $total; } - public function get_empty_tables() + public function get_empty_tables() { return $this->CI->Dinner_table->get_empty_tables(); } + + public function check_for_cash_rounding($total) + { + $cash_decimals = $this->CI->config->config['cash_decimals']; + $cash_rounding_code = $this->CI->config->config['cash_rounding_code']; + $rounded_total = $total; + + if($cash_rounding_code == Rounding_code::HALF_UP) + { + $rounded_total = round ( $total, $cash_decimals, PHP_ROUND_HALF_UP); + } + elseif($cash_rounding_code == Rounding_code::HALF_DOWN) + { + $rounded_total = round ( $total, $cash_decimals, PHP_ROUND_HALF_DOWN); + } + elseif($cash_rounding_code == Rounding_code::HALF_EVEN) + { + $rounded_total = round ( $total, $cash_decimals, PHP_ROUND_HALF_EVEN); + } + elseif($cash_rounding_code == Rounding_code::HALF_ODD) + { + $rounded_total = round ( $total, $cash_decimals, PHP_ROUND_HALF_UP); + } + elseif($cash_rounding_code == Rounding_code::ROUND_UP) + { + $fig = (int) str_pad('1', $cash_decimals, '0'); + $rounded_total = (ceil($total * $fig) / $fig); + } + elseif($cash_rounding_code == Rounding_code::ROUND_DOWN) + { + $fig = (int) str_pad('1', $cash_decimals, '0'); + $rounded_total = (floor($total * $fig) / $fig); + } + elseif($cash_rounding_code == Rounding_code::HALF_FIVE) + { + $rounded_total = round($total / 5) * 5; + } + + return $rounded_total; + } } ?> diff --git a/application/libraries/Tax_lib.php b/application/libraries/Tax_lib.php new file mode 100644 index 000000000..aefd54f89 --- /dev/null +++ b/application/libraries/Tax_lib.php @@ -0,0 +1,320 @@ +CI =& get_instance(); + $this->CI->load->library('sale_lib'); + } + + public function get_tax_types() + { + return array( + TAX_TYPE_SALES => $this->CI->lang->line('taxes_sales_tax'), + TAX_TYPE_SALES_BY_INVOICE => $this->CI->lang->line('taxes_sales_tax_by_invoice'), + TAX_TYPE_VAT => $this->CI->lang->line('taxes_vat_tax') + ); + } + + /* + * Compute the tax basis and returns the tax amount + */ + public function get_item_sales_tax($quantity, $price, $discount_percentage, $tax_percentage, $rounding_code) + { + $tax_decimals = $this->CI->config->config['tax_decimals']; + + // The tax basis should be returned at the currency scale + $tax_basis = $this->CI->sale_lib->get_item_total($quantity, $price, $discount_percentage, TRUE); + + return $this->get_sales_tax_for_amount($tax_basis, $tax_percentage, $rounding_code, $tax_decimals); + } + + /* + * Computes the item level sales tax amount for a given tax basis + */ + public function get_sales_tax_for_amount($tax_basis, $tax_percentage, $rounding_code, $decimals) + { + + $tax_fraction = bcdiv($tax_percentage, 100); + + $tax_amount = bcmul($tax_basis, $tax_fraction); + $rounded_tax_amount = $tax_amount; + + if($rounding_code == Rounding_code::HALF_UP) + { + $rounded_tax_amount = round ( $tax_amount, $decimals, PHP_ROUND_HALF_UP); + } + elseif($rounding_code == Rounding_code::HALF_DOWN) + { + $rounded_tax_amount = round ( $tax_amount, $decimals, PHP_ROUND_HALF_DOWN); + } + elseif($rounding_code == Rounding_code::HALF_EVEN) + { + $rounded_tax_amount = round ( $tax_amount, $decimals, PHP_ROUND_HALF_EVEN); + } + elseif($rounding_code == Rounding_code::HALF_ODD) + { + $rounded_tax_amount = round ( $tax_amount, $decimals, PHP_ROUND_HALF_UP); + } + elseif($rounding_code == Rounding_code::ROUND_UP) // ROUND_UP + { + $fig = (int) str_pad('1', $decimals, '0'); + $rounded_tax_amount = (ceil($tax_amount * $fig) / $fig); + } + elseif($rounding_code == Rounding_code::ROUND_DOWN) // ROUND_DOWN + { + $fig = (int) str_pad('1', $decimals, '0'); + $rounded_tax_amount = (floor($tax_amount * $fig) / $fig); + } + elseif($rounding_code == Rounding_code::HALF_FIVE) + { + $rounded_tax_amount = round($tax_amount / 5) * 5; + } + + return $rounded_tax_amount; + } + + /* + * Updates the sales_tax array which is later saved to the `sales_taxes` table and used for printing taxes on receipts and invoices + */ + 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 ($item_tax_amount != 0) { + 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' => $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); + } + } + } + + /* + * If invoice taxing (as opposed to invoice_item_taxing) rules apply then recalculate the sales tax after tax group totals are final + */ + 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); + } + + $currency_decimals = $this->CI->config->config['currency_decimals']; + + foreach($sales_taxes as $row_number => $sales_tax) + { + $sales_tax['sale_tax_amount'] = get_sales_tax_for_amount($sales_tax['sale_tax_basis'], $sales_tax['sale_tax_rate'], $sales_tax['rounding_code'], $currency_decimals); + } + } + + /* + * Apply rounding rules to the accumulated sales tax amounts + */ + 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); + } + + $currency_decimals = $this->CI->config->config['currency_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 == Rounding_code::HALF_UP) + { + $rounded_sale_tax_amount = round ( $sale_tax_amount, $currency_decimals, PHP_ROUND_HALF_UP); + } + elseif($rounding_code == Rounding_code::HALF_DOWN) + { + $rounded_sale_tax_amount = round ( $sale_tax_amount, $currency_decimals, PHP_ROUND_HALF_DOWN); + } + elseif($rounding_code == Rounding_code::HALF_EVEN) + { + $rounded_sale_tax_amount = round ( $sale_tax_amount, $currency_decimals, PHP_ROUND_HALF_EVEN); + } + elseif($rounding_code == Rounding_code::HALF_ODD) + { + $rounded_sale_tax_amount = round ( $sale_tax_amount, $currency_decimals, PHP_ROUND_HALF_UP); + } + elseif($rounding_code == Rounding_code::ROUND_UP) + { + $fig = (int) str_pad('1', $currency_decimals, '0'); + $rounded_sale_tax_amount = (ceil($sale_tax_amount * $fig) / $fig); + } + elseif($rounding_code == Rounding_code::ROUND_DOWN) + { + $fig = (int) str_pad('1', $currency_decimals, '0'); + $rounded_sale_tax_amount = (floor($sale_tax_amount * $fig) / $fig); + } + elseif($rounding_code == Rounding_code::HALF_FIVE) + { + $rounded_sale_tax_amount = round($sale_tax_amount / 5) * 5; + } + + $sales_taxes[$row_number]['sale_tax_amount'] = $rounded_sale_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 + */ + public function apply_sales_tax(&$item, &$city, &$state, &$sales_tax_code, $register_mode, $sale_id, &$sales_taxes) + { + $tax_code = $this->get_applicable_tax_mode($register_mode, $city, $state, $sales_tax_code); + + // If tax code cannot be determined or the price is zero then skip this item + if($tax_code != '' && $item['price'] != 0) + { + $tax_rate = 0.0000; + $rounding_code = Rounding_code::HALF_UP; + + $tax_code_obj = $this->CI->Tax->get_info($tax_code); + $tax_category_id = $item['tax_category_id']; + + if($tax_category_id != 0) + { + $tax_rate_info = $this->CI->Tax->get_rate_info($tax_code, $tax_category_id); + if ($tax_rate_info) + { + $tax_rate = $tax_rate_info->tax_rate; + $rounding_code = $tax_rate_info->rounding_code; + } + else + { + $tax_rate = $tax_code_obj->tax_rate; + $rounding_code = $tax_code_obj->rounding_code; + } + } + + if($tax_category_id != 0) + { + $tax_rate_info = $this->CI->Tax->get_rate_info($tax_code, $tax_category_id); + $tax_rate = $tax_rate_info->tax_rate; + $rounding_code = $tax_rate_info->rounding_code; + $tax_group_sequence = $tax_rate_info->tax_group_sequence; + $tax_category = $tax_rate_info->tax_category; + } + else + { + $tax_rate = $tax_code_obj->tax_rate; + $rounding_code = $tax_code_obj->rounding_code; + $tax_group_sequence = $tax_code_obj->tax_group_sequence; + $tax_category = $tax_code_obj->tax_category; + } + + $tax_decimals = $this->CI->config->config['tax_decimals']; + + // The tax basis should be returned at the currency scale + $tax_basis = $this->CI->sale_lib->get_item_total($item['quantity'], $item['price'], $item['discount'], TRUE); + $tax_amount = $this->get_sales_tax_for_amount($tax_basis, $tax_rate, $rounding_code, $tax_decimals); + + $tax_group = (float)$tax_rate . '% ' . $tax_category; + $tax_type = Tax_lib::TAX_TYPE_SALES; + + if($tax_amount != 0) + { + $this->update_sales_taxes($sales_taxes, $tax_type, $tax_group, $tax_rate, $tax_basis, $tax_amount, $tax_group_sequence, $rounding_code, $sale_id, $tax_category, $tax_code); + } + + // input : register_mode + // input : city + // input : state + // input : sales_tax_code + // input : $item['price'] + // input : $item['tax_category_id'] + // input : $item['quantity'] + // input : $item['price'] + // input : $item['discount'] + // both : $sales_taxes + // output : tax_details['tax_rate'] + // output : tax_details['rounding_code'] + // output : tax_details['tax_group_sequence'] + // output : tax_details['tax_code'] + + $tax_details = array('item_tax_amount' => $tax_amount, 'tax_group' => $tax_group, 'tax_name' => $tax_category, 'tax_rate' => $tax_rate, 'rounding_code' => $rounding_code, 'tax_group_sequence' => $tax_group_sequence, 'tax_code' => $tax_code); + + return $tax_details; + } + else + { + $tax_details = array('item_tax_amount' => 0.0000, 'tax_group' => '', 'tax_name' => '', 'tax_rate' => 0.0000, 'rounding_code' => '0', 'tax_group_sequence' => '0', 'tax_code' => ''); + return $tax_details; + } + } + + public function get_applicable_tax_mode($register_mode, $city, $state, $sales_tax_code) + { + if ($register_mode == "SALE") + { + $tax_code = $this->CI->config->config['default_origin_tax_code']; // overrides customer assigned code + } + else + { + if ($sales_tax_code == '') + { + $tax_code = $this->CI->Tax->get_sales_tax_code($city, $state); + } + else + { + // Use the customer assigned tax rate code + $tax_code = $sales_tax_code; + } + } + return $tax_code; + } + + function clean($string) { + $string = str_replace(' ', '-', $string); // Replaces all spaces with hyphens. + + return preg_replace('/[^A-Za-z0-9\-]/', '', $string); // Removes special chars. + } +} + +?> \ No newline at end of file diff --git a/application/libraries/tokens/Token_suspended_invoice_count.php b/application/libraries/tokens/Token_suspended_invoice_count.php index 5b317943e..2809103b4 100644 --- a/application/libraries/tokens/Token_suspended_invoice_count.php +++ b/application/libraries/tokens/Token_suspended_invoice_count.php @@ -8,12 +8,12 @@ class Token_suspended_invoice_count extends Token { parent::__construct(); $this->CI =& get_instance(); - $this->CI->load->model('Sale_suspended'); + $this->CI->load->model('Sale'); } public function get_value() { - return $this->CI->Sale_suspended->get_invoice_count(); + return $this->CI->Sale->get_suspended_invoice_count(); } } diff --git a/application/models/Customer.php b/application/models/Customer.php index 4e35a4b83..f2c6dce08 100644 --- a/application/models/Customer.php +++ b/application/models/Customer.php @@ -97,6 +97,7 @@ class Customer extends Person $this->db->from('sales'); $this->db->join('sales_payments', 'sales.sale_id = sales_payments.sale_id'); $this->db->where('sales.customer_id', $customer_id); + $this->db->where('sale_status',0); return $this->db->get()->row(); } @@ -123,7 +124,7 @@ class Customer extends Person //Run these queries as a transaction, we want to make sure we do all or nothing $this->db->trans_start(); - + if(parent::save($person_data, $customer_id)) { if(!$customer_id || !$this->exists($customer_id)) @@ -139,7 +140,7 @@ class Customer extends Person } $this->db->trans_complete(); - + $success &= $this->db->trans_status(); return $success; diff --git a/application/models/Dinner_table.php b/application/models/Dinner_table.php index e34d65b48..96a3c4acd 100644 --- a/application/models/Dinner_table.php +++ b/application/models/Dinner_table.php @@ -57,17 +57,17 @@ class Dinner_table extends CI_Model public function get_name($dinner_table_id) { - $this->db->from('dinner_tables'); - $this->db->where('dinner_table_id', $dinner_table_id); - - $row = $this->db->get()->row(); - - if(isset($row->name)) + if($dinner_table_id == null || empty($dinner_table_id)) { - return $row->name; + return ''; } + else + { + $this->db->from('dinner_tables'); + $this->db->where('dinner_table_id',$dinner_table_id); - return ''; + return $this->db->get()->row()->name; + } } public function get_all() diff --git a/application/models/Item.php b/application/models/Item.php index daf5666c4..a1c64299a 100644 --- a/application/models/Item.php +++ b/application/models/Item.php @@ -414,6 +414,7 @@ class Item extends CI_Model $this->db->select('item_id, item_number'); $this->db->from('items'); $this->db->where('deleted', $filters['is_deleted']); + $this->db->where("item_type = '0'"); // standard, exclude kit items since kits will be picked up later $this->db->like('item_number', $search); $this->db->order_by('item_number', 'asc'); foreach($this->db->get()->result() as $row) @@ -427,6 +428,7 @@ class Item extends CI_Model $this->db->select('category'); $this->db->from('items'); $this->db->where('deleted', $filters['is_deleted']); + $this->db->where("item_type = '0'"); // standard, exclude kit items since kits will be picked up later $this->db->distinct(); $this->db->like('category', $search); $this->db->order_by('category', 'asc'); @@ -441,6 +443,7 @@ class Item extends CI_Model $this->db->like('company_name', $search); // restrict to non deleted companies only if is_deleted is FALSE $this->db->where('deleted', $filters['is_deleted']); + $this->db->where("item_type = '0'"); // standard, exclude kit items since kits will be picked up later $this->db->distinct(); $this->db->order_by('company_name', 'asc'); foreach($this->db->get()->result() as $row) @@ -452,6 +455,7 @@ class Item extends CI_Model $this->db->select('item_id, name, description'); $this->db->from('items'); $this->db->where('deleted', $filters['is_deleted']); + $this->db->where("item_type = '0'"); // standard, exclude kit items since kits will be picked up later $this->db->like('description', $search); $this->db->order_by('description', 'asc'); foreach($this->db->get()->result() as $row) @@ -480,6 +484,7 @@ class Item extends CI_Model $this->db->or_like('custom10', $search); $this->db->group_end(); $this->db->where('deleted', $filters['is_deleted']); + $this->db->where("item_type = '0'"); // standard, exclude kit items since kits will be picked up later foreach($this->db->get()->result() as $row) { $suggestions[] = array('value' => $row->item_id, 'label' => $row->name); @@ -487,6 +492,118 @@ class Item extends CI_Model } } + + //only return $limit suggestions + if(count($suggestions > $limit)) + { + $suggestions = array_slice($suggestions, 0,$limit); + } + + return $suggestions; + } + + + public function get_stock_search_suggestions($search, $filters = array('is_deleted' => FALSE, 'search_custom' => FALSE), $unique = FALSE, $limit = 25) + { + $suggestions = array(); + + $this->db->select('item_id, name'); + $this->db->from('items'); + $this->db->where('deleted', $filters['is_deleted']); + $this->db->where("item_type = '0'"); // standard, exclude kit items since kits will be picked up later + $this->db->where("stock_type = '0'"); // stocked items only + $this->db->like('name', $search); + $this->db->order_by('name', 'asc'); + foreach($this->db->get()->result() as $row) + { + $suggestions[] = array('value' => $row->item_id, 'label' => $row->name); + } + + $this->db->select('item_id, item_number'); + $this->db->from('items'); + $this->db->where('deleted', $filters['is_deleted']); + $this->db->where("item_type = '0'"); // standard, exclude kit items since kits will be picked up later + $this->db->where("stock_type = '0'"); // stocked items only + $this->db->like('item_number', $search); + $this->db->order_by('item_number', 'asc'); + foreach($this->db->get()->result() as $row) + { + $suggestions[] = array('value' => $row->item_id, 'label' => $row->item_number); + } + + if(!$unique) + { + //Search by category + $this->db->select('category'); + $this->db->from('items'); + $this->db->where('deleted', $filters['is_deleted']); + $this->db->where("item_type = '0'"); // standard, exclude kit items since kits will be picked up later + $this->db->where("stock_type = '0'"); // stocked items only + $this->db->distinct(); + $this->db->like('category', $search); + $this->db->order_by('category', 'asc'); + foreach($this->db->get()->result() as $row) + { + $suggestions[] = array('label' => $row->category); + } + + //Search by supplier + $this->db->select('company_name'); + $this->db->from('suppliers'); + $this->db->like('company_name', $search); + // restrict to non deleted companies only if is_deleted is FALSE + $this->db->where('deleted', $filters['is_deleted']); + $this->db->distinct(); + $this->db->order_by('company_name', 'asc'); + foreach($this->db->get()->result() as $row) + { + $suggestions[] = array('label' => $row->company_name); + } + + //Search by description + $this->db->select('item_id, name, description'); + $this->db->from('items'); + $this->db->where('deleted', $filters['is_deleted']); + $this->db->where("item_type = '0'"); // standard, exclude kit items since kits will be picked up later + $this->db->where("stock_type = '0'"); // stocked items only + $this->db->like('description', $search); + $this->db->order_by('description', 'asc'); + foreach($this->db->get()->result() as $row) + { + $entry = array('value' => $row->item_id, 'label' => $row->name); + if(!array_walk($suggestions, function($value, $label) use ($entry) { return $entry['label'] != $label; } )) + { + $suggestions[] = $entry; + } + } + + //Search by custom fields + if($filters['search_custom'] != FALSE) + { + $this->db->from('items'); + $this->db->group_start(); + $this->db->like('custom1', $search); + $this->db->or_like('custom2', $search); + $this->db->or_like('custom3', $search); + $this->db->or_like('custom4', $search); + $this->db->or_like('custom5', $search); + $this->db->or_like('custom6', $search); + $this->db->or_like('custom7', $search); + $this->db->or_like('custom8', $search); + $this->db->or_like('custom9', $search); + $this->db->or_like('custom10', $search); + $this->db->group_end(); + $this->db->where("item_type = '0'"); // standard, exclude kit items since kits will be picked up later + $this->db->where("stock_type = '0'"); // stocked items only + $this->db->where('deleted', $filters['is_deleted']); + foreach($this->db->get()->result() as $row) + { + $suggestions[] = array('value' => $row->item_id, 'label' => $row->name); + } + } + } + + //only return $limit suggestions if(count($suggestions > $limit)) { diff --git a/application/models/Sale.php b/application/models/Sale.php index 30d9ab4e7..4b0b61413 100644 --- a/application/models/Sale.php +++ b/application/models/Sale.php @@ -42,6 +42,7 @@ class Sale extends CI_Model '( SELECT sales_items_taxes.sale_id AS sale_id, sales_items_taxes.item_id AS item_id, + sales_items_taxes.line AS line, ' . " IFNULL(ROUND($sale_tax, $decimals), 0) AS tax " . ' @@ -51,7 +52,7 @@ class Sale extends CI_Model INNER JOIN ' . $this->db->dbprefix('sales_items') . ' AS sales_items ON sales_items.sale_id = sales_items_taxes.sale_id AND sales_items.line = sales_items_taxes.line WHERE sales.sale_id = ' . $this->db->escape($sale_id) . ' - GROUP BY sale_id, item_id + GROUP BY sale_id, item_id, line )' ); @@ -60,7 +61,9 @@ class Sale extends CI_Model MAX(DATE(sales.sale_time)) AS sale_date, MAX(sales.sale_time) AS sale_time, MAX(sales.comment) AS comment, + MAX(sales.sale_status) AS sale_status, MAX(sales.invoice_number) AS invoice_number, + MAX(sales.quote_number) AS quote_number, MAX(sales.employee_id) AS employee_id, MAX(sales.customer_id) AS customer_id, MAX(CONCAT(customer_p.first_name, " ", customer_p.last_name)) AS customer_name, @@ -81,7 +84,9 @@ class Sale extends CI_Model $this->db->join('people AS customer_p', 'sales.customer_id = customer_p.person_id', 'left'); $this->db->join('customers AS customer', 'sales.customer_id = customer.person_id', 'left'); $this->db->join('sales_payments_temp AS payments', 'sales.sale_id = payments.sale_id', 'left outer'); - $this->db->join('sales_items_taxes_temp AS sales_items_taxes', 'sales_items.sale_id = sales_items_taxes.sale_id AND sales_items.item_id = sales_items_taxes.item_id', 'left outer'); + $this->db->join('sales_items_taxes_temp AS sales_items_taxes', + 'sales_items.sale_id = sales_items_taxes.sale_id AND sales_items.item_id = sales_items_taxes.item_id AND sales_items.line = sales_items_taxes.line', + 'left outer'); $this->db->where('sales.sale_id', $sale_id); @@ -108,11 +113,11 @@ class Sale extends CI_Model if (empty($this->config->item('date_or_time_format'))) { - $where .= 'DATE(sales.sale_time) BETWEEN ' . $this->db->escape($filters['start_date']) . ' AND ' . $this->db->escape($filters['end_date']) . ' '; + $where .= 'DATE(sales.sale_time) BETWEEN ' . $this->db->escape($filters['start_date']) . ' AND ' . $this->db->escape($filters['end_date']) . ' AND sales.sale_status = 0 '; } else { - $where .= 'sales.sale_time BETWEEN ' . $this->db->escape(rawurldecode($filters['start_date'])) . ' AND ' . $this->db->escape(rawurldecode($filters['end_date'])) . ' '; + $where .= 'sales.sale_time BETWEEN ' . $this->db->escape(rawurldecode($filters['start_date'])) . ' AND ' . $this->db->escape(rawurldecode($filters['end_date'])) . ' AND sales.sale_status = 0 '; } // NOTE: temporary tables are created to speed up searches due to the fact that they are ortogonal to the main query @@ -156,6 +161,7 @@ class Sale extends CI_Model ( SELECT sales_items_taxes.sale_id AS sale_id, sales_items_taxes.item_id AS item_id, + sales_items_taxes.line AS line, ' . " IFNULL(ROUND($sale_tax, $decimals), 0) AS tax " . ' @@ -165,7 +171,7 @@ class Sale extends CI_Model INNER JOIN ' . $this->db->dbprefix('sales_items') . ' AS sales_items ON sales_items.sale_id = sales_items_taxes.sale_id AND sales_items.line = sales_items_taxes.line WHERE ' . $where . ' - GROUP BY sale_id, item_id + GROUP BY sale_id, item_id, line )' ); @@ -174,6 +180,7 @@ class Sale extends CI_Model MAX(DATE(sales.sale_time)) AS sale_date, MAX(sales.sale_time) AS sale_time, MAX(sales.invoice_number) AS invoice_number, + MAX(sales.quote_number) AS quote_number, SUM(sales_items.quantity_purchased) AS items_purchased, MAX(CONCAT(customer_p.first_name, " ", customer_p.last_name)) AS customer_name, MAX(customer.company_name) AS company_name, @@ -195,7 +202,9 @@ class Sale extends CI_Model $this->db->join('people AS customer_p', 'sales.customer_id = customer_p.person_id', 'left'); $this->db->join('customers AS customer', 'sales.customer_id = customer.person_id', 'left'); $this->db->join('sales_payments_temp AS payments', 'sales.sale_id = payments.sale_id', 'left outer'); - $this->db->join('sales_items_taxes_temp AS sales_items_taxes', 'sales_items.sale_id = sales_items_taxes.sale_id AND sales_items.item_id = sales_items_taxes.item_id', 'left outer'); + $this->db->join('sales_items_taxes_temp AS sales_items_taxes', + 'sales_items.sale_id = sales_items_taxes.sale_id AND sales_items.item_id = sales_items_taxes.item_id AND sales_items.line = sales_items_taxes.line', + 'left outer'); $this->db->where($where); @@ -486,8 +495,15 @@ class Sale extends CI_Model return $success; } - public function save($items, $customer_id, $employee_id, $comment, $invoice_number, $payments, $dinner_table, $sale_id = FALSE) + + /* + * Save the sale information after the sales is complete but before the final document is printed + * The sales_taxes variable needs to be initialized to an empty array before calling + */ + public function save(&$sale_status, &$items, $customer_id, $employee_id, $comment, $invoice_number, $quote_number, $payments, $dinner_table, &$sales_taxes, $sale_id = FALSE) { + $tax_decimals = $this->config->item('tax_decimals'); + if(count($items) == 0) { return -1; @@ -499,7 +515,9 @@ class Sale extends CI_Model 'employee_id' => $employee_id, 'comment' => $comment, 'invoice_number' => $invoice_number, - 'dinner_table_id'=> $dinner_table + 'quote_number' => $quote_number, + 'dinner_table_id'=> $dinner_table, + 'sale_status' => $sale_status ); // Run these queries as a transaction, we want to make sure we do all or nothing @@ -554,6 +572,10 @@ class Sale extends CI_Model } } + $customer = $this->Customer->get_info($customer_id); + + $sales_taxes = array(); + foreach($items as $line=>$item) { $cur_item_info = $this->Item->get_info($item['item_id']); @@ -602,22 +624,82 @@ class Sale extends CI_Model ); $this->Inventory->insert($inv_data); - $customer = $this->Customer->get_info($customer_id); + // Calculate taxes and save the tax information for the sale. Return the result for printing + if($customer_id == -1 || $customer->taxable) { + if($this->config->item('tax_included')) + { + $tax_type = Tax_lib::TAX_TYPE_VAT; + } + else + { + $tax_type = Tax_lib::TAX_TYPE_SALES; + } + $rounding_code = Rounding_code::HALF_UP; // half adjust + $tax_group_sequence = 0; + $item_total = $this->sale_lib->get_item_total($item['quantity'], $item['price'], $item['discount'], TRUE); + $tax_basis = $item_total; + $item_tax_amount = 0; + foreach($this->Item_taxes->get_info($item['item_id']) as $row) { - $this->db->insert('sales_items_taxes', array( - 'sale_id' => $sale_id, - 'item_id' => $item['item_id'], - 'line' => $item['line'], - 'name' => $row['name'], - 'percent' => $row['percent'] - )); + + $sales_items_taxes = array( + 'sale_id' => $sale_id, + 'item_id' => $item['item_id'], + 'line' => $item['line'], + 'name' => character_limiter($row['name'], 255), + 'percent' => $row['percent'], + 'tax_type' => $tax_type, + 'rounding_code' => $rounding_code, + 'cascade_tax' => 0, + 'cascade_sequence' => 0, + 'item_tax_amount' => 0 + ); + + // This computes tax for each line item and adds it to the tax type total + $tax_group = (float)$row['percent'] . '% ' . $row['name']; + $tax_basis = $this->sale_lib->get_item_total($item['quantity'], $item['price'], $item['discount'], TRUE); + + if($this->config->item('tax_included')) + { + $tax_type = Tax_lib::TAX_TYPE_VAT; + $item_tax_amount = $this->sale_lib->get_item_tax($item['quantity'], $item['price'], $item['discount'],$row['percent']); + } + elseif($this->config->item('customer_sales_tax_support') == '0') + { + $tax_type = Tax_lib::TAX_TYPE_SALES; + $item_tax_amount = $this->tax_lib->get_sales_tax_for_amount($tax_basis, $row['percent'], '0', $tax_decimals); + } + else + { + $tax_type = Tax_lib::TAX_TYPE_SALES; + } + + $sales_items_taxes['item_tax_amount'] = $item_tax_amount; + if ($item_tax_amount != 0) + { + $this->db->insert('sales_items_taxes', $sales_items_taxes); + $this->tax_lib->update_sales_taxes($sales_taxes, $tax_type, $tax_group, $row['percent'], $tax_basis, $item_tax_amount, $tax_group_sequence, $rounding_code, $sale_id, $row['name'], ''); + $tax_group_sequence += 1; + } + + } + + if($this->config->item('customer_sales_tax_support') == '1') + { + $this->save_sales_item_tax($customer, $sale_id, $item, $item_total, $sales_taxes, $sequence, $cur_item_info->tax_category_id); } } } + if($customer_id == -1 || $customer->taxable) + { + $this->tax_lib->round_sales_taxes($sales_taxes); + $this->save_sales_tax($sales_taxes); + } + $this->db->trans_complete(); if($this->db->trans_status() === FALSE) @@ -628,6 +710,47 @@ class Sale extends CI_Model return $sale_id; } + /** + * Apply customer sales tax if the customer sales tax is enabledl + * The original tax is still supported if the user configures it, + * but it won't make sense unless it's used exclusively for the purpose + * of VAT tax which becomes a price component. VAT taxes must still be reported + * as a separate tax entry on the invoice. + */ + public function save_sales_item_tax(&$customer, &$sale_id, &$item, $tax_basis, &$sales_taxes, &$sequence, $tax_category_id) + { + // if customer sales tax is enabled then update sales_items_taxes with the + if($this->config->item('customer_sales_tax_support') == '1') + { + $register_mode = $this->config->item('default_register_mode'); + $tax_details = $this->tax_lib->apply_sales_tax($item, $customer->city, $customer->state, $customer->sales_tax_code, $register_mode, $sale_id, $sales_taxes); + + $sales_items_taxes = array( + 'sale_id' => $sale_id, + 'item_id' => $item['item_id'], + 'line' => $item['line'], + 'name' => $tax_details['tax_name'], + 'percent' => $tax_details['tax_rate'], + 'tax_type' => Tax_lib::TAX_TYPE_SALES, + 'rounding_code' => $tax_details['rounding_code'], + 'cascade_tax' => 0, + 'cascade_sequence' => 0, + 'item_tax_amount' => $tax_details['item_tax_amount'] + ); + + $this->db->insert('sales_items_taxes', $sales_items_taxes); + + } + } + + private function save_sales_tax(&$sales_taxes) + { + foreach($sales_taxes as $line=>$sales_tax) + { + $this->db->insert('sales_taxes', $sales_tax); + } + } + public function delete_list($sale_ids, $employee_id, $update_inventory = TRUE) { $result = TRUE; @@ -815,8 +938,8 @@ class Sale extends CI_Model // TODO change to use new quote_number field public function check_quote_number_exists($quote_number, $sale_id = '') { - $this->db->from('sales_suspended'); - $this->db->where('invoice_number', $quote_number); + $this->db->from('sales'); + $this->db->where('quote_number', $quote_number); if(!empty($sale_id)) { $this->db->where('sale_id !=', $sale_id); @@ -894,6 +1017,7 @@ class Sale extends CI_Model ( SELECT sales_items_taxes.sale_id AS sale_id, sales_items_taxes.item_id AS item_id, + sales_items_taxes.line AS line, ' . " IFNULL(ROUND($sale_tax, $decimals), 0) AS tax " . ' @@ -902,8 +1026,8 @@ class Sale extends CI_Model ON sales.sale_id = sales_items_taxes.sale_id INNER JOIN ' . $this->db->dbprefix('sales_items') . ' AS sales_items ON sales_items.sale_id = sales_items_taxes.sale_id AND sales_items.line = sales_items_taxes.line - WHERE ' . $where . ' - GROUP BY sale_id, item_id + WHERE sales.sale_status = 0 AND ' . $where . ' + GROUP BY sale_id, item_id, line )' ); @@ -917,7 +1041,7 @@ class Sale extends CI_Model FROM ' . $this->db->dbprefix('sales_payments') . ' AS payments INNER JOIN ' . $this->db->dbprefix('sales') . ' AS sales ON sales.sale_id = payments.sale_id - WHERE ' . $where . ' + WHERE sales.sale_status = 0 AND ' . $where . ' GROUP BY payments.sale_id )' ); @@ -931,6 +1055,7 @@ class Sale extends CI_Model sales.sale_id AS sale_id, MAX(sales.comment) AS comment, MAX(sales.invoice_number) AS invoice_number, + MAX(sales.quote_number) AS quote_number, MAX(sales.customer_id) AS customer_id, MAX(CONCAT(customer_p.first_name, " ", customer_p.last_name)) AS customer_name, MAX(customer_p.first_name) AS customer_first_name, @@ -977,8 +1102,8 @@ class Sale extends CI_Model LEFT OUTER JOIN ' . $this->db->dbprefix('people') . ' AS employee ON sales.employee_id = employee.person_id LEFT OUTER JOIN ' . $this->db->dbprefix('sales_items_taxes_temp') . ' AS sales_items_taxes - ON sales_items.sale_id = sales_items_taxes.sale_id AND sales_items.item_id = sales_items_taxes.item_id - WHERE ' . $where . ' + ON sales_items.sale_id = sales_items_taxes.sale_id AND sales_items.item_id = sales_items_taxes.item_id AND sales_items.line = sales_items_taxes.line + WHERE sales.sale_status = 0 AND ' . $where . ' GROUP BY sale_id, item_id, line )' ); @@ -987,5 +1112,92 @@ class Sale extends CI_Model $this->db->query('DROP TEMPORARY TABLE IF EXISTS ' . $this->db->dbprefix('sales_payments_temp')); $this->db->query('DROP TEMPORARY TABLE IF EXISTS ' . $this->db->dbprefix('sales_items_taxes_temp')); } + + /* + * Retrieves all sales that are in a suspended state + */ + public function get_all_suspended($customer_id = NULL) + { + if ($customer_id == -1) + { + $query = $this->db->query('select sale_id, sale_id as suspended_sale_id, sale_status, sale_time, dinner_table_id, customer_id, comment from ' + . $this->db->dbprefix('sales') . ' where sale_status = 1 ' + . ' union select sale_id, sale_id*-1 as suspended_sale_id, 2 as sale_status, sale_time, dinner_table_id, customer_id, comment from ' + . $this->db->dbprefix('sales_suspended')); + } + else + { + $query = $this->db->query('select sale_id, sale_id as suspended_sale_id, sale_status, sale_time, dinner_table_id, customer_id, comment from ' + . $this->db->dbprefix('sales') . ' where sale_status = 1 and customer_id = ' . $customer_id + . ' union select sale_id, sale_id*-1 as suspended_sale_id, 2 as sale_status, sale_time, dinner_table_id, customer_id, comment from ' + . $this->db->dbprefix('sales_suspended') . ' where customer_id = ' . $customer_id); + } + + return $query->result_array(); + + } + + /* + * get the dinner table for the selected sale + */ + public function get_dinner_table($sale_id) + { + $this->db->from('sales'); + $this->db->where('sale_id', $sale_id); + + return $this->db->get()->row()->dinner_table_id; + } + + /* + * Gets total of suspended invoices rows + */ + public function get_suspended_invoice_count() + { + $this->db->from('sales'); + $this->db->where('invoice_number IS NOT NULL'); + $this->db->where('sale_status', '1'); + + return $this->db->count_all_results(); + } + + /* + * This will remove a selected sale from the sales table. + * This function should only be called for suspended sales that are being restored to the current cart + */ + public function delete_suspended_sale($sale_id) + { + //Run these queries as a transaction, we want to make sure we do all or nothing + $this->db->trans_start(); + + $dinner_table = $this->get_dinner_table($sale_id); + $dinner_table_data = array( + 'status' => 0 + ); + + $this->db->where('dinner_table_id',$dinner_table); + $this->db->update('dinner_tables', $dinner_table_data); + + $this->db->delete('sales_payments', array('sale_id' => $sale_id)); + $this->db->delete('sales_items_taxes', array('sale_id' => $sale_id)); + $this->db->delete('sales_items', array('sale_id' => $sale_id)); + $this->db->delete('sales_taxes', array('sale_id' => $sale_id)); + $this->db->delete('sales', array('sale_id' => $sale_id)); + + $this->db->trans_complete(); + + return $this->db->trans_status(); + } + + public function get_suspended_sale_info($sale_id) + { + $this->db->from('sales'); + $this->db->where('sale_id', $sale_id); + $this->db->join('people', 'people.person_id = sales_suspended.customer_id', 'LEFT'); + + return $this->db->get(); + } + + + } ?> diff --git a/application/models/Tax.php b/application/models/Tax.php new file mode 100644 index 000000000..4ed6e0bfd --- /dev/null +++ b/application/models/Tax.php @@ -0,0 +1,375 @@ +db->from('tax_codes'); + $this->db->where('tax_code', $tax_code); + + return ($this->db->get()->num_rows() == 1); + } + + public function tax_rate_exists($tax_code, $tax_category_id) + { + $this->db->from('tax_code_rates'); + $this->db->where('rate_tax_code', $tax_code); + $this->db->where('rate_tax_category_id', $tax_category_id); + + return ($this->db->get()->num_rows() == 1); + } + + /* + Gets total of rows + */ + public function get_total_rows() + { + $this->db->from('tax_codes'); + + return $this->db->count_all_results(); + } + + /* + Gets information about a particular tax_code + */ + public function get_info($tax_code) + { + $this->db->from('tax_codes'); + $this->db->join('tax_code_rates', + 'tax_code = rate_tax_code and rate_tax_category_id = 0', 'LEFT'); + $this->db->join('tax_categories', + 'rate_tax_category_id = tax_category_id'); + $this->db->where('tax_code', $tax_code); + + $query = $this->db->get(); + + if($query->num_rows() == 1) + { + return $query->row(); + } + else + { + //Get empty base parent object + $tax_code_obj = new stdClass(); + + //Get all the fields from tax_codes table + foreach($this->db->list_fields('tax_codes') as $field) + { + $tax_code_obj->$field = ''; + } + foreach($this->db->list_fields('tax_code_rates') as $field) + { + $tax_code_obj->$field = ''; + } + + return $tax_code_obj; + } + } + + /* +Gets information about a particular tax_code +*/ + public function get_rate_info($tax_code, $tax_category_id) + { + $this->db->from('tax_code_rates'); + $this->db->join('tax_categories', + 'rate_tax_category_id = tax_category_id'); + $this->db->where('rate_tax_code', $tax_code); + $this->db->where('rate_tax_category_id', $tax_category_id); + + $query = $this->db->get(); + + if($query->num_rows() == 1) + { + return $query->row(); + } + else + { + //Get empty base parent object + $tax_rate_obj = new stdClass(); + + //Get all the fields from tax_codes table + foreach($this->db->list_fields('tax_code_rates') as $field) + { + $tax_rate_obj->$field = ''; + } + //Get all the fields from tax_code_rates table + foreach($this->db->list_fields('tax_categories') as $field) + { + $tax_rate_obj->$field = ''; + } + + return $tax_rate_obj; + } + } + + /* + * Gets the tax code to use for a given customer + */ + public function get_sales_tax_code($city = '', $state = '') + { + // if tax code using both city and state cannot be found then try again using just the state + // if the state tax code cannot be found then try again using blanks for both + $this->db->from('tax_codes'); + $this->db->where('city', $city); + $this->db->where('state', $state); + $this->db->where('tax_code_type', '0'); // sales tax + + $query = $this->db->get(); + + if($query->num_rows() == 1) + { + return $query->row()->tax_code; + } + else + { + $this->db->from('tax_codes'); + $this->db->where('city', ''); + $this->db->where('state', $state); + $this->db->where('tax_code_type', '0'); // sales tax + + $query = $this->db->get(); + + if($query->num_rows() == 1) + { + return $query->row()->tax_code; + } + else + { + return $this->config->item('default_origin_tax_code'); + } + } + + return FALSE; + } + + /* + Inserts or updates a tax_codes entry + */ + public function save(&$tax_code_data, $tax_rate_data, $tax_code = -1) + { + if(!$this->exists($tax_code)) + { + if($this->db->insert('tax_codes', $tax_code_data)) + { + $this->save_tax_rates($tax_rate_data, $tax_code); + return TRUE; + } + return FALSE; + } + + $this->db->where('tax_code', $tax_code); + if ($this->db->update('tax_codes', $tax_code_data)) + { + $this->save_tax_rates($tax_rate_data, $tax_code); + return TRUE; + } + else + { + return FALSE; + } + } + + public function save_tax_rates(&$tax_rate_data, $tax_code) + { + if(!$this->tax_rate_exists($tax_code, $tax_rate_data['rate_tax_category_id'])) + { + if($this->db->insert('tax_code_rates', $tax_rate_data)) + { + return TRUE; + } + return FALSE; + } + + $this->db->where('rate_tax_code', $tax_code); + $this->db->where('rate_tax_category_id', $tax_rate_data['rate_tax_category_id']); + + return $this->db->update('tax_code_rates', $tax_rate_data); + } + + /* + Inserts or updates an item kit's items + */ + public function save_tax_rate_exceptions(&$tax_rate_data, $tax_code) + { + $success = TRUE; + + //Run these queries as a transaction, we want to make sure we do all or nothing + + $this->db->trans_start(); + + // Delete all exceptions for the given tax_code + $this->delete_tax_rate_exceptions($tax_code); + + if ($tax_rate_data != NULL) { + foreach ($tax_rate_data as $row) { + $row['rate_tax_code'] = $tax_code; + $success &= $this->db->insert('tax_code_rates', $row); + } + } + + $this->db->trans_complete(); + + $success &= $this->db->trans_status(); + + return $success; + } + + /* + Deletes one tax_codes entry + */ + public function delete($tax_code) + { + return $this->db->delete('tax_codes', array('tax_code' => $tax_code)); + } + + /* + Deletes a list of tax codes + */ + public function delete_list($tax_codes) + { + $this->db->where_in('tax_code', $tax_codes); + + return $this->db->delete('tax_codes'); + } + + + /* + Deletes all tax_rate_exceptions for given tax codes + */ + public function delete_tax_rate_exceptions($tax_code) + { + $this->db->where('rate_tax_code', $tax_code); + $this->db->where('rate_tax_category_id !=', 0); + + return $this->db->delete('tax_code_rates'); + } + + /* + Performs a search on tax_codes + */ + public function search($search, $rows = 0, $limit_from = 0, $sort = 'tax_code', $order = 'asc') + { + $this->db->from('tax_codes'); + $this->db->join('tax_code_rates', + 'tax_code = rate_tax_code and rate_tax_category_id = 0', 'LEFT'); + if (!empty($search)) { + $this->db->like('tax_code', $search); + $this->db->or_like('tax_code_name', $search); + } + $this->db->order_by($sort, $order); + + if($rows > 0) + { + $this->db->limit($rows, $limit_from); + } + + return $this->db->get(); + } + + /* + Gets tax_codes + */ + public function get_found_rows($search) + { + $this->db->from('tax_codes'); + if (!empty($search)) { + $this->db->like('tax_code', $search); + $this->db->or_like('tax_code_name', $search); + } + + return $this->db->get()->num_rows(); + } + + public function get_tax_code_type_name($tax_code_type) { + if ($tax_code_type == '0') + { + return $this->lang->line('taxes_sales_tax'); + } + else + { + return $this->lang->line('taxes_vat_tax'); + } + } + + public function get_sales_tax_codes_search_suggestions($search, $limit = 25) + { + + $suggestions = array(); + + $this->db->from('tax_codes'); + if (!empty($search)) { + $this->db->like('tax_code', $search); + $this->db->or_like('tax_code_name', $search); + } + $this->db->order_by('tax_code_name', 'asc'); + + foreach($this->db->get()->result() as $row) + { + $suggestions[] = array('value' => $row->tax_code, 'label' => ($row->tax_code . ' ' . $row->tax_code_name)); + } + + //only return $limit suggestions + if(count($suggestions > $limit)) + { + $suggestions = array_slice($suggestions, 0,$limit); + } + + return $suggestions; + } + + public function get_tax_category_suggestions($search) + { + $suggestions = array(); + + $this->db->from('tax_categories'); + $this->db->where('tax_category_id !=', 0); + if (!empty($search)) { + $this->db->like('tax_category', '%'.$search.'%'); + } + $this->db->order_by('tax_category', 'asc'); + + foreach($this->db->get()->result() as $row) + { + $suggestions[] = array('value' => $row->tax_category_id, 'label' => $row->tax_category); + } + + return $suggestions; + } + + public function get_tax_category($tax_category_id) + { + $this->db->select('tax_category'); + $this->db->from('tax_categories'); + $this->db->where('tax_category_id', $tax_category_id); + return $this->db->get()->row()->tax_category; + } + + public function get_all_tax_categories() + { + $this->db->from('tax_categories'); + $this->db->order_by('tax_category_id'); + return $this->db->get(); + } + + public function get_tax_category_id($tax_category) + { + $this->db->select('tax_category_id'); + $this->db->from('tax_categories'); + return $this->db->get()->row()->tax_category_id; + } + + public function get_tax_code_rate_exceptions($tax_code) + { + $this->db->from('tax_code_rates'); + $this->db->join('tax_categories', 'rate_tax_category_id = tax_category_id'); + $this->db->where('rate_tax_code', $tax_code); + $this->db->where('rate_tax_category_id !=', 0); + $this->db->order_by('tax_category', 'asc'); + + return $this->db->get()->result_array(); + } +} +?> \ No newline at end of file diff --git a/application/models/enums/Rounding_code.php b/application/models/enums/Rounding_code.php new file mode 100644 index 000000000..a72bc9aff --- /dev/null +++ b/application/models/enums/Rounding_code.php @@ -0,0 +1,80 @@ +load->helper('language'); + return array( + Rounding_code::HALF_UP => lang('enum_half_up'), + Rounding_code::HALF_DOWN => lang('enum_half_down'), + Rounding_code::HALF_EVEN => lang('enum_half_even'), + Rounding_code::HALF_ODD => lang('enum_half_odd'), + Rounding_code::ROUND_UP => lang('enum_round_up'), + Rounding_code::ROUND_DOWN => lang('enum_round_down'), + Rounding_code::HALF_FIVE => lang('enum_half_five') + ); + } + + public static function get_rounding_code_name($rounding_code) + { + $CI =& get_instance(); + $CI->load->helper('language'); + if($rounding_code == Rounding_code::HALF_UP) + { + return lang('enum_half_up'); + } + elseif($rounding_code == Rounding_code::HALF_DOWN) + { + return lang('enum_half_down'); + } + elseif($rounding_code == Rounding_code::HALF_EVEN) + { + return lang('enum_half_even'); + } + elseif($rounding_code == Rounding_code::HALF_ODD) + { + return lang('enum_half_odd'); + } + elseif($rounding_code == Rounding_code::ROUND_UP) + { + return lang('enum_round_up'); + } + elseif($rounding_code == Rounding_code::ROUND_DOWN) + { + return lang('enum_round_down'); + } + elseif($rounding_code == Rounding_code::HALF_FIVE) + { + return lang('enum_half_five'); + } + else + { + return lang('common_unknown'); + } + } + + public static function get_html_rounding_options() + { + $CI =& get_instance(); + $CI->load->helper('language'); + $x = "" . + "" . + "" . + "" . + "" . + "" . + ""; + + return $x; + } +} \ No newline at end of file diff --git a/application/models/reports/Summary_report.php b/application/models/reports/Summary_report.php index a612d8288..9ea2ff073 100644 --- a/application/models/reports/Summary_report.php +++ b/application/models/reports/Summary_report.php @@ -53,6 +53,7 @@ abstract class Summary_report extends Report ( SELECT sales_items_taxes.sale_id AS sale_id, sales_items_taxes.item_id AS item_id, + sales_items_taxes.line AS line, ' . " IFNULL(ROUND($sale_tax, $decimals), 0) AS tax " . ' @@ -61,8 +62,8 @@ abstract class Summary_report extends Report ON sales.sale_id = sales_items_taxes.sale_id INNER JOIN ' . $this->db->dbprefix('sales_items') . ' AS sales_items ON sales_items.sale_id = sales_items_taxes.sale_id AND sales_items.line = sales_items_taxes.line - WHERE ' . $where . ' - GROUP BY sale_id, item_id + WHERE sale_status = 0 AND ' . $where . ' + GROUP BY sale_id, item_id, line )' ); @@ -79,7 +80,9 @@ abstract class Summary_report extends Report { $this->db->from('sales_items AS sales_items'); $this->db->join('sales AS sales', 'sales_items.sale_id = sales.sale_id', 'inner'); - $this->db->join('sales_items_taxes_temp AS sales_items_taxes', 'sales_items.sale_id = sales_items_taxes.sale_id AND sales_items.item_id = sales_items_taxes.item_id', 'left outer'); + $this->db->join('sales_items_taxes_temp AS sales_items_taxes', + 'sales_items.sale_id = sales_items_taxes.sale_id AND sales_items.item_id = sales_items_taxes.item_id AND sales_items.line = sales_items_taxes.line', + 'left outer'); } private function _common_where(array $inputs) diff --git a/application/models/reports/Summary_taxes.php b/application/models/reports/Summary_taxes.php index b25808847..2576c3246 100644 --- a/application/models/reports/Summary_taxes.php +++ b/application/models/reports/Summary_taxes.php @@ -23,11 +23,11 @@ class Summary_taxes extends Summary_report { if(empty($this->config->item('date_or_time_format'))) { - $this->db->where('DATE(sales.sale_time) BETWEEN ' . $this->db->escape($inputs['start_date']) . ' AND ' . $this->db->escape($inputs['end_date'])); + $this->db->where('sale_status = 0 AND DATE(sales.sale_time) BETWEEN ' . $this->db->escape($inputs['start_date']) . ' AND ' . $this->db->escape($inputs['end_date'])); } else { - $this->db->where('sales.sale_time BETWEEN ' . $this->db->escape(rawurldecode($inputs['start_date'])) . ' AND ' . $this->db->escape(rawurldecode($inputs['end_date']))); + $this->db->where('sale_status = 0 AND sales.sale_time BETWEEN ' . $this->db->escape(rawurldecode($inputs['start_date'])) . ' AND ' . $this->db->escape(rawurldecode($inputs['end_date']))); } } @@ -37,11 +37,11 @@ class Summary_taxes extends Summary_report if(empty($this->config->item('date_or_time_format'))) { - $where .= 'WHERE DATE(sale_time) BETWEEN ' . $this->db->escape($inputs['start_date']) . ' AND ' . $this->db->escape($inputs['end_date']); + $where .= 'WHERE sale_status = 0 AND DATE(sale_time) BETWEEN ' . $this->db->escape($inputs['start_date']) . ' AND ' . $this->db->escape($inputs['end_date']); } else { - $where .= 'WHERE sale_time BETWEEN ' . $this->db->escape(rawurldecode($inputs['start_date'])) . ' AND ' . $this->db->escape(rawurldecode($inputs['end_date'])); + $where .= 'WHERE sale_status = 0 AND sale_time BETWEEN ' . $this->db->escape(rawurldecode($inputs['start_date'])) . ' AND ' . $this->db->escape(rawurldecode($inputs['end_date'])); } if($this->config->item('tax_included')) diff --git a/application/views/configs/general_config.php b/application/views/configs/general_config.php index adbabf8a6..ea95753e4 100644 --- a/application/views/configs/general_config.php +++ b/application/views/configs/general_config.php @@ -12,19 +12,19 @@