From 9bf8989960b0c5063da574e7503e9bbd4798f22f Mon Sep 17 00:00:00 2001 From: Steve Ireland Date: Sat, 4 Feb 2017 23:13:23 -0500 Subject: [PATCH 1/2] This will add support for preparing quotes and generating invoices prior to payments --- application/controllers/Config.php | 21 +- application/controllers/Sales.php | 465 +++++++++++++----- application/language/en/config_lang.php | 4 + application/language/en/sales_lang.php | 7 + application/libraries/Sale_lib.php | 183 ++++++- application/libraries/Token_lib.php | 108 ++++ application/libraries/tokens/Token.php | 58 +++ .../libraries/tokens/Token_customer.php | 31 ++ .../libraries/tokens/Token_invoice_count.php | 18 + .../tokens/Token_invoice_sequence.php | 17 + .../libraries/tokens/Token_quote_sequence.php | 17 + .../tokens/Token_suspended_invoice_count.php | 20 + .../tokens/Token_year_invoice_count.php | 18 + application/models/Appconfig.php | 14 + application/models/Sale.php | 16 +- application/models/Sale_suspended.php | 5 +- application/views/configs/invoice_config.php | 52 +- application/views/sales/quote.php | 177 +++++++ application/views/sales/quote_email.php | 128 +++++ application/views/sales/register.php | 47 +- database/3.0.2_to_3.1.0.sql | 7 + database/database.sql | 5 + database/tables.sql | 5 + 23 files changed, 1242 insertions(+), 181 deletions(-) create mode 100644 application/libraries/Token_lib.php create mode 100644 application/libraries/tokens/Token.php create mode 100644 application/libraries/tokens/Token_customer.php create mode 100644 application/libraries/tokens/Token_invoice_count.php create mode 100644 application/libraries/tokens/Token_invoice_sequence.php create mode 100644 application/libraries/tokens/Token_quote_sequence.php create mode 100644 application/libraries/tokens/Token_suspended_invoice_count.php create mode 100644 application/libraries/tokens/Token_year_invoice_count.php create mode 100644 application/views/sales/quote.php create mode 100644 application/views/sales/quote_email.php diff --git a/application/controllers/Config.php b/application/controllers/Config.php index 732d57246..5f444cf12 100644 --- a/application/controllers/Config.php +++ b/application/controllers/Config.php @@ -196,6 +196,7 @@ class Config extends Secure_Controller $data['support_barcode'] = $this->barcode_lib->get_list_barcodes(); $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 = $this->xss_clean($data); @@ -470,16 +471,34 @@ class Config extends Secure_Controller { $batch_save_data = array ( 'invoice_enable' => $this->input->post('invoice_enable') != NULL, + 'default_register_mode' => $this->input->post('default_register_mode'), 'sales_invoice_format' => $this->input->post('sales_invoice_format'), + 'sales_quote_format' => $this->input->post('sales_quote_format'), 'recv_invoice_format' => $this->input->post('recv_invoice_format'), 'invoice_default_comments' => $this->input->post('invoice_default_comments'), 'invoice_email_message' => $this->input->post('invoice_email_message'), - 'line_sequence' => $this->input->post('line_sequence') + 'line_sequence' => $this->input->post('line_sequence'), + 'last_used_invoice_number' =>$this->input->post('last_used_invoice_number'), + 'last_used_quote_number' =>$this->input->post('last_used_quote_number') ); $result = $this->Appconfig->batch_save($batch_save_data); $success = $result ? TRUE : FALSE; + // Update the register mode with the latest change so that if the user + // switches immediately back to the register the mode reflects the change + if ($success == TRUE) + { + if ($this->config->item('invoice_enable') == '1') + { + $this->sale_lib->set_mode($batch_save_data['default_register_mode']); + } + else + { + $this->sale_lib->set_mode('sale'); + } + } + echo json_encode(array('success' => $success, 'message' => $this->lang->line('config_saved_' . ($success ? '' : 'un') . 'successfully'))); } diff --git a/application/controllers/Sales.php b/application/controllers/Sales.php index 4e99ebc3c..bfb89d2b0 100644 --- a/application/controllers/Sales.php +++ b/application/controllers/Sales.php @@ -1,4 +1,4 @@ -load->library('sale_lib'); $this->load->library('barcode_lib'); $this->load->library('email_lib'); + $this->load->library('token_lib'); + } public function index() { $this->_reload(); } - + public function manage() { $person_id = $this->session->userdata('person_id'); @@ -34,7 +36,7 @@ class Sales extends Secure_Controller if($this->config->item('invoice_enable') == TRUE) { $data['filters'] = array('only_cash' => $this->lang->line('sales_cash_filter'), - 'only_invoices' => $this->lang->line('sales_invoice_filter')); + 'only_invoices' => $this->lang->line('sales_invoice_filter')); } else { @@ -44,7 +46,7 @@ class Sales extends Secure_Controller $this->load->view('sales/manage', $data); } } - + public function get_row($row_id) { $sale_info = $this->Sale->get_info($row_id)->row(); @@ -56,18 +58,18 @@ class Sales extends Secure_Controller public function search() { $search = $this->input->get('search'); - $limit = $this->input->get('limit'); + $limit = $this->input->get('limit'); $offset = $this->input->get('offset'); - $sort = $this->input->get('sort'); - $order = $this->input->get('order'); + $sort = $this->input->get('sort'); + $order = $this->input->get('order'); $filters = array('sale_type' => 'all', - 'location_id' => 'all', - 'start_date' => $this->input->get('start_date'), - 'end_date' => $this->input->get('end_date'), - 'only_cash' => FALSE, - 'only_invoices' => $this->config->item('invoice_enable') && $this->input->get('only_invoices'), - 'is_valid_receipt' => $this->Sale->is_valid_receipt($search)); + 'location_id' => 'all', + 'start_date' => $this->input->get('start_date'), + 'end_date' => $this->input->get('end_date'), + 'only_cash' => FALSE, + 'only_invoices' => $this->config->item('invoice_enable') && $this->input->get('only_invoices'), + 'is_valid_receipt' => $this->Sale->is_valid_receipt($search)); // check if any filter is set in the multiselect dropdown $filledup = array_fill_keys($this->input->get('filters'), TRUE); @@ -79,12 +81,12 @@ class Sales extends Secure_Controller $payment_summary = $this->xss_clean(get_sales_manage_payments_summary($payments, $sales, $this)); $data_rows = array(); - foreach($sales->result() as $sale) + foreach ($sales->result() as $sale) { $data_rows[] = $this->xss_clean(get_sale_data_row($sale, $this)); } - if($total_rows > 0) + if ($total_rows > 0) { $data_rows[] = $this->xss_clean(get_sale_data_last_row($sales, $this)); } @@ -97,14 +99,14 @@ class Sales extends Secure_Controller $suggestions = array(); $receipt = $search = $this->input->get('term') != '' ? $this->input->get('term') : NULL; - if($this->sale_lib->get_mode() == 'return' && $this->Sale->is_valid_receipt($receipt)) + if ($this->sale_lib->get_mode() == 'return' && $this->Sale->is_valid_receipt($receipt)) { // if a valid receipt or invoice was found the search term will be replaced with a receipt number (POS #) $suggestions[] = $receipt; } $suggestions = array_merge($suggestions, $this->Item->get_search_suggestions($search, array('search_custom' => FALSE, 'is_deleted' => FALSE), TRUE)); $suggestions = array_merge($suggestions, $this->Item_kit->get_search_suggestions($search)); - + $suggestions = $this->xss_clean($suggestions); echo json_encode($suggestions); @@ -113,9 +115,9 @@ class Sales extends Secure_Controller public function suggest_search() { $search = $this->input->post('term') != '' ? $this->input->post('term') : NULL; - + $suggestions = $this->xss_clean($this->Sale->get_search_suggestions($search)); - + echo json_encode($suggestions); } @@ -125,27 +127,26 @@ class Sales extends Secure_Controller if($this->Customer->exists($customer_id)) { $this->sale_lib->set_customer($customer_id); - $discount_percent = $this->Customer->get_info($customer_id)->discount_percent; // apply customer default discount to items that have 0 discount - if($discount_percent != '') - { + if ($discount_percent != '') + { $this->sale_lib->apply_customer_discount($discount_percent); } } - + $this->_reload(); } public function change_mode() { $stock_location = $this->input->post('stock_location'); - if (!$stock_location || $stock_location == $this->sale_lib->get_sale_location()) + if(!$stock_location || $stock_location == $this->sale_lib->get_sale_location()) { $mode = $this->input->post('mode'); $this->sale_lib->set_mode($mode); - } + } elseif($this->Stock_location->is_allowed_location($stock_location, 'sales')) { $this->sale_lib->set_sale_location($stock_location); @@ -153,27 +154,27 @@ class Sales extends Secure_Controller $this->_reload(); } - - public function set_comment() + + public function set_comment() { $this->sale_lib->set_comment($this->input->post('comment')); } - + public function set_invoice_number() { $this->sale_lib->set_invoice_number($this->input->post('sales_invoice_number')); } - + public function set_invoice_number_enabled() { $this->sale_lib->set_invoice_number_enabled($this->input->post('sales_invoice_number_enabled')); } - + public function set_print_after_sale() { $this->sale_lib->set_print_after_sale($this->input->post('sales_print_after_sale')); } - + public function set_email_receipt() { $this->sale_lib->set_email_receipt($this->input->post('email_receipt')); @@ -189,7 +190,7 @@ class Sales extends Secure_Controller if($this->form_validation->run() == FALSE) { - if($payment_type == $this->lang->line('sales_giftcard')) + if ($payment_type == $this->lang->line('sales_giftcard')) { $data['error'] = $this->lang->line('sales_must_enter_numeric_giftcard'); } @@ -200,7 +201,7 @@ class Sales extends Secure_Controller } else { - if($payment_type == $this->lang->line('sales_giftcard')) + if ($payment_type == $this->lang->line('sales_giftcard')) { // in case of giftcard payment the register input amount_tendered becomes the giftcard number $giftcard_num = $this->input->post('amount_tendered'); @@ -209,7 +210,7 @@ class Sales extends Secure_Controller $payment_type = $payment_type . ':' . $giftcard_num; $current_payments_with_giftcard = isset($payments[$payment_type]) ? $payments[$payment_type]['payment_amount'] : 0; $cur_giftcard_value = $this->Giftcard->get_giftcard_value($giftcard_num); - + if(($cur_giftcard_value - $current_payments_with_giftcard) <= 0) { $data['error'] = $this->lang->line('giftcards_remaining_balance', $giftcard_num, to_currency($cur_giftcard_value)); @@ -221,7 +222,7 @@ class Sales extends Secure_Controller $this->sale_lib->set_giftcard_remainder($new_giftcard_value); $new_giftcard_value = str_replace('$', '\$', to_currency($new_giftcard_value)); $data['warning'] = $this->lang->line('giftcards_remaining_balance', $giftcard_num, $new_giftcard_value); - $amount_tendered = min( $this->sale_lib->get_amount_due(), $this->Giftcard->get_giftcard_value($giftcard_num) ); + $amount_tendered = min($this->sale_lib->get_amount_due(), $this->Giftcard->get_giftcard_value($giftcard_num)); $this->sale_lib->add_payment($payment_type, $amount_tendered); } @@ -229,7 +230,6 @@ class Sales extends Secure_Controller else { $amount_tendered = $this->input->post('amount_tendered'); - $this->sale_lib->add_payment($payment_type, $amount_tendered); } } @@ -253,18 +253,18 @@ class Sales extends Secure_Controller // check if any discount is assigned to the selected customer $customer_id = $this->sale_lib->get_customer(); - if($customer_id != -1) + if ($customer_id != -1) { // load the customer discount if any $discount_percent = $this->Customer->get_info($customer_id)->discount_percent; - if($discount_percent != '') + if ($discount_percent != '') { $discount = $discount_percent; } } // if the customer discount is 0 or no customer is selected apply the default sales discount - if($discount == 0) + if ($discount == 0) { $discount = $this->config->item('default_sales_discount'); } @@ -274,7 +274,7 @@ class Sales extends Secure_Controller $item_location = $this->sale_lib->get_sale_location(); $item_id_or_number_or_item_kit_or_receipt = $this->input->post('item'); - if($mode == 'return' && $this->Sale->is_valid_receipt($item_id_or_number_or_item_kit_or_receipt)) + if ($mode == 'return' && $this->Sale->is_valid_receipt($item_id_or_number_or_item_kit_or_receipt)) { $this->sale_lib->return_entire_sale($item_id_or_number_or_item_kit_or_receipt); } @@ -299,23 +299,23 @@ class Sales extends Secure_Controller if(!empty($kit_item_id)) { - if(!$this->sale_lib->add_item($kit_item_id, $quantity, $item_location, $discount, $price, null, null, null, $print_option)) + if(!$this->sale_lib->add_item($kit_item_id, $quantity, $item_location, $discount, $price, null, null, null, $print_option, $stock_type)) { $data['error'] = $this->lang->line('sales_unable_to_add_item'); } else { - $data['warning'] = $this->sale_lib->out_of_stock($item_id_or_number_or_item_kit_or_receipt, $item_location); + $data['warning'] = $this->sale_lib->out_of_stock($item_kit_id, $item_location); } } // Add item kit items to order $stock_warning = null; - if(!$this->sale_lib->add_item_kit($item_id_or_number_or_item_kit_or_receipt, $item_location, $discount, $price_option, $kit_print_option, $stock_warning)) + if (!$this->sale_lib->add_item_kit($item_id_or_number_or_item_kit_or_receipt, $item_location, $discount, $price_option, $kit_print_option, $stock_warning)) { $data['error'] = $this->lang->line('sales_unable_to_add_item'); } - elseif ($stock_warning != null) + elseif($stock_warning != null) { $data['warning'] = $stock_warning; } @@ -331,7 +331,6 @@ class Sales extends Secure_Controller $data['warning'] = $this->sale_lib->out_of_stock($item_id_or_number_or_item_kit_or_receipt, $item_location); } } - $this->_reload($data); } @@ -350,7 +349,7 @@ class Sales extends Secure_Controller $discount = parse_decimals($this->input->post('discount')); $item_location = $this->input->post('location'); - if($this->form_validation->run() != FALSE) + if ($this->form_validation->run() != FALSE) { $this->sale_lib->edit_item($item_id, $description, $serialnumber, $quantity, $discount, $price); } @@ -380,10 +379,14 @@ class Sales extends Secure_Controller $this->_reload(); } + public function complete_receipt() + { + $this->complete(); + } + public function complete() { $data = array(); - $data['cart'] = $this->sale_lib->get_cart(); $data['subtotal'] = $this->sale_lib->get_subtotal(); $data['discounted_subtotal'] = $this->sale_lib->get_subtotal(TRUE); @@ -401,32 +404,114 @@ class Sales extends Secure_Controller $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]; + $data['employee'] = $employee_info->first_name . ' ' . $employee_info->last_name[0]; $data['company_info'] = implode("\n", array( $this->config->item('address'), $this->config->item('phone'), $this->config->item('account_number') )); + $data['invoice_number_enabled'] = $this->sale_lib->is_invoice_mode(); + $data['cur_giftcard_value'] = $this->sale_lib->get_giftcard_remainder(); + $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(); $customer_info = $this->_load_customer_data($customer_id, $data); - $invoice_number = $this->_substitute_invoice_number($customer_info); - if($this->sale_lib->is_invoice_number_enabled() && $this->Sale->check_invoice_number_exists($invoice_number)) + if ($this->sale_lib->is_invoice_mode() || $data['invoice_number_enabled'] == true) { - $data['error'] = $this->lang->line('sales_invoice_number_duplicate'); + // 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()) + { + $this->sale_lib->set_invoice_number($this->input->post('invoice_number'), $keep_custom = TRUE); + $invoice_format = $this->sale_lib->get_invoice_number(); + if (empty($invoice_format)) + { + $invoice_format = $this->config->item('sales_invoice_format'); + } + } + else + { + $invoice_format = $this->config->item('sales_invoice_format'); + } + $invoice_number = $this->token_lib->render($invoice_format); - $this->_reload($data); + $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)) + { + $data['error'] = $this->lang->line('sales_invoice_number_duplicate'); + $this->_reload($data); + } + else + { + $data['invoice_number'] = $invoice_number; + $data['quote_number'] = $quote_number; + + // 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['sale_id'] = 'POS ' . $data['sale_id_num']; + + // Resort and filter cart lines for printing + $data['cart'] = $this->sale_lib->sort_and_filter_cart($data['cart']); + + $data = $this->xss_clean($data); + + if($data['sale_id_num'] == -1) + { + $data['error_message'] = $this->lang->line('sales_transaction_failed'); + } + else + { + $data['barcode'] = $this->barcode_lib->generate_receipt_barcode($data['sale_id']); + $this->load->view('sales/invoice', $data); + $this->sale_lib->clear_all(); + } + } } - else + elseif($this->sale_lib->is_quote_mode()) { - $invoice_number = $this->sale_lib->is_invoice_number_enabled() ? $invoice_number : NULL; - $data['invoice_number'] = $invoice_number; - $data['sale_id_num'] = $this->Sale->save($data['cart'], $customer_id, $employee_id, $data['comments'], $invoice_number, $data['payments']); + $quote_number = $this->sale_lib->get_quote_number(); + if($quote_number == null) + { + // generate quote number + $quote_format = $this->config->item('sales_quote_format'); + $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)) + { + $data['error'] = $this->lang->line('sales_quote_number_duplicate'); + $this->_reload($data); + } + else + { + $data['invoice_number'] = $invoice_number; + $data['quote_number'] = $quote_number; + + $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->load->view('sales/quote', $data); + $this->sale_lib->clear_mode(); + $this->sale_lib->clear_all(); + } + } + 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['sale_id'] = 'POS ' . $data['sale_id_num']; - + $data['cart'] = $this->sale_lib->sort_and_filter_cart($data['cart']); $data = $this->xss_clean($data); - + if($data['sale_id_num'] == -1) { $data['error_message'] = $this->lang->line('sales_transaction_failed'); @@ -434,25 +519,13 @@ class Sales extends Secure_Controller else { $data['barcode'] = $this->barcode_lib->generate_receipt_barcode($data['sale_id']); - } - $data['cur_giftcard_value'] = $this->sale_lib->get_giftcard_remainder(); - $data['print_after_sale'] = $this->sale_lib->is_print_after_sale(); - $data['email_receipt'] = $this->sale_lib->get_email_receipt(); + // Reload (sorted) and filter the cart line items for printing purposes + $data['cart'] = $this->get_filtered($this->sale_lib->get_cart_reordered($data['sale_id_num'])); - // Reload (sorted) and filter the cart line items for printing purposes - $data['cart'] = $this->get_filtered($this->sale_lib->get_cart_reordered($data['sale_id_num'])); - - if($this->sale_lib->is_invoice_number_enabled()) - { - $this->load->view('sales/invoice', $data); - } - else - { $this->load->view('sales/receipt', $data); + $this->sale_lib->clear_all(); } - - $this->sale_lib->clear_all(); } } @@ -463,7 +536,7 @@ class Sales extends Secure_Controller $result = FALSE; $message = $this->lang->line('sales_invoice_no_email'); - if(!empty($sale_data['customer_email'])) + if (!empty($sale_data['customer_email'])) { $to = $sale_data['customer_email']; $subject = $this->lang->line('sales_invoice') . ' ' . $sale_data['invoice_number']; @@ -471,16 +544,16 @@ class Sales extends Secure_Controller $text = $this->config->item('invoice_email_message'); $text = str_replace('$INV', $sale_data['invoice_number'], $text); $text = str_replace('$CO', 'POS ' . $sale_data['sale_id'], $text); - $text = $this->_substitute_customer($text, (object) $sale_data); + $text = $this->_substitute_customer($text, (object)$sale_data); // generate email attachment: invoice in pdf format $html = $this->load->view('sales/invoice_email', $sale_data, TRUE); // load pdf helper $this->load->helper(array('dompdf', 'file')); - $filename = sys_get_temp_dir() . '/' . $this->lang->line('sales_invoice') . '-' . str_replace('/', '-' , $sale_data['invoice_number']) . '.pdf'; + $filename = sys_get_temp_dir() . '/' . $this->lang->line('sales_invoice') . '-' . str_replace('/', '-', $sale_data['invoice_number']) . '.pdf'; if(file_put_contents($filename, pdf_create($html)) !== FALSE) { - $result = $this->email_lib->sendEmail($to, $subject, $text, $filename); + $result = $this->email_lib->sendEmail($to, $subject, $text, $filename); } $message = $this->lang->line($result ? 'sales_invoice_sent' : 'sales_invoice_unsent') . ' ' . $to; @@ -493,6 +566,43 @@ class Sales extends Secure_Controller return $result; } + public function send_quote($sale_id) + { + $sale_data = $this->_load_sale_data($sale_id); + + $result = FALSE; + $message = $this->lang->line('sales_invoice_no_email'); + + if(!empty($sale_data['customer_email'])) + { + $to = $sale_data['customer_email']; + $subject = $this->lang->line('sales_quote') . ' ' . $sale_data['quote_number']; + + $text = $this->config->item('invoice_email_message'); + $text = str_replace('$INV', $sale_data['invoice_number'], $text); + $text = str_replace('$CO', 'POS ' . $sale_data['sale_id'], $text); + $text = $this->_substitute_customer($text, (object)$sale_data); + + // generate email attachment: invoice in pdf format + $html = $this->load->view('sales/quote_email', $sale_data, TRUE); + // load pdf helper + $this->load->helper(array('dompdf', 'file')); + $filename = sys_get_temp_dir() . '/' . $this->lang->line('sales_quote') . '-' . str_replace('/', '-', $sale_data['quote_number']) . '.pdf'; + if(file_put_contents($filename, pdf_create($html)) !== FALSE) + { + $result = $this->email_lib->sendEmail($to, $subject, $text, $filename); + } + + $message = $this->lang->line($result ? 'sales_quote_sent' : 'sales_quote_unsent') . ' ' . $to; + } + + echo json_encode(array('success' => $result, 'message' => $message, 'id' => $sale_id)); + + $this->sale_lib->clear_all(); + + return $result; + } + public function send_receipt($sale_id) { $sale_data = $this->_load_sale_data($sale_id); @@ -500,7 +610,7 @@ class Sales extends Secure_Controller $result = FALSE; $message = $this->lang->line('sales_receipt_no_email'); - if(!empty($sale_data['customer_email'])) + if (!empty($sale_data['customer_email'])) { $sale_data['barcode'] = $this->barcode_lib->generate_receipt_barcode($sale_data['sale_id']); @@ -508,7 +618,7 @@ class Sales extends Secure_Controller $subject = $this->lang->line('sales_receipt'); $text = $this->load->view('sales/receipt_email', $sale_data, TRUE); - + $result = $this->email_lib->sendEmail($to, $subject, $text); $message = $this->lang->line($result ? 'sales_receipt_sent' : 'sales_receipt_unsent') . ' ' . $to; @@ -524,7 +634,7 @@ class Sales extends Secure_Controller private function _substitute_variable($text, $variable, $object, $function) { // don't query if this variable isn't used - if(strstr($text, $variable)) + if (strstr($text, $variable)) { $value = call_user_func(array($object, $function)); $text = str_replace($variable, $value, $text); @@ -537,12 +647,12 @@ class Sales extends Secure_Controller { // substitute customer info $customer_id = $this->sale_lib->get_customer(); - if($customer_id != -1 && $customer_info != '') + if ($customer_id != -1 && $customer_info != '') { $text = str_replace('$CU', $customer_info->first_name . ' ' . $customer_info->last_name, $text); $words = preg_split("/\s+/", trim($customer_info->first_name . ' ' . $customer_info->last_name)); $acronym = ''; - foreach($words as $w) + foreach($words as $w) { $acronym .= $w[0]; } @@ -560,10 +670,18 @@ class Sales extends Secure_Controller return $this->sale_lib->get_invoice_number() != $invoice_number; } + private function _is_custom_quote_number($customer_info) + { + $quote_number = $this->config->config['sales_quote_format']; + $quote_number = $this->_substitute_variables($quote_number, $customer_info); + + return $this->sale_lib->get_quote_number() != $quote_number; + } + private function _substitute_variables($text, $customer_info) { $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, '$CO', $this->Sale, 'get_invoice_count'); $text = $this->_substitute_variable($text, '$SCO', $this->Sale_suspended, 'get_invoice_count'); $text = strftime($text); $text = $this->_substitute_customer($text, $customer_info); @@ -580,14 +698,23 @@ class Sales extends Secure_Controller return $this->sale_lib->get_invoice_number(); } + private function _substitute_quote_number($customer_info) + { + $quote_number = $this->config->config['sales_quote_format']; + $quote_number = $this->_substitute_variables($quote_number, $customer_info); + $this->sale_lib->set_quote_number($quote_number, TRUE); + + return $this->sale_lib->get_quote_number(); + } + private function _load_customer_data($customer_id, &$data, $totals = FALSE) - { + { $customer_info = ''; - if($customer_id != -1) + if ($customer_id != -1) { $customer_info = $this->Customer->get_info($customer_id); - if(isset($customer_info->company_name)) + if (isset($customer_info->company_name)) { $data['customer'] = $customer_info->company_name; } @@ -601,7 +728,7 @@ class Sales extends Secure_Controller $data['customer_address'] = $customer_info->address_1; if(!empty($customer_info->zip) or !empty($customer_info->city)) { - $data['customer_location'] = $customer_info->zip . ' ' . $customer_info->city; + $data['customer_location'] = $customer_info->zip . ' ' . $customer_info->city; } else { @@ -609,10 +736,9 @@ class Sales extends Secure_Controller } $data['customer_account_number'] = $customer_info->account_number; $data['customer_discount_percent'] = $customer_info->discount_percent; - if($totals) + if ($totals) { $cust_totals = $this->Customer->get_totals($customer_id); - $data['customer_total'] = $cust_totals->total; } $data['customer_info'] = implode("\n", array( @@ -661,14 +787,36 @@ class Sales extends Secure_Controller )); $data['barcode'] = $this->barcode_lib->generate_receipt_barcode($data['sale_id']); $data['print_after_sale'] = FALSE; - + if($this->sale_lib->get_mode() == 'sale_invoice') + { + $data['mode_label'] = $this->lang->line('sales_invoice'); + } + elseif($this->sale_lib->get_mode() == 'sale_quote') + { + $data['mode_label'] = $this->lang->line('sales_quote'); + } return $this->xss_clean($data); } private function _reload($data = array()) - { - $data['cart'] = $this->sale_lib->get_cart(); - $data['modes'] = array('sale' => $this->lang->line('sales_sale'), 'return' => $this->lang->line('sales_return')); + { + $data['cart'] = $this->sale_lib->get_cart(); + $customer_info = $this->_load_customer_data($this->sale_lib->get_customer(), $data, TRUE); + + if ($this->config->item('invoice_enable') == '0') + { + $data['modes'] = array( + 'sale' => $this->lang->line('sales_sale'), + 'return' => $this->lang->line('sales_return')); + } + else + { + $data['modes'] = array( + 'sale' => $this->lang->line('sales_sale'), + 'sale_invoice' => $this->lang->line('sales_sale_by_invoice'), + 'sale_quote' => $this->lang->line('sales_quote'), + 'return' => $this->lang->line('sales_return')); + } $data['mode'] = $this->sale_lib->get_mode(); $data['stock_locations'] = $this->Stock_location->get_allowed_locations('sales'); $data['stock_location'] = $this->sale_lib->get_sale_location(); @@ -683,15 +831,37 @@ class Sales extends Secure_Controller $data['amount_due'] = $this->sale_lib->get_amount_due(); $data['payments'] = $this->sale_lib->get_payments(); $data['payment_options'] = $this->Sale->get_payment_options(); + $quote_number = $this->sale_lib->get_quote_number(); + if ($quote_number != NULL) + { + $data['quote_number'] = $quote_number; + } $data['items_module_allowed'] = $this->Employee->has_grant('items', $this->Employee->get_logged_in_employee_info()->person_id); - $customer_info = $this->_load_customer_data($this->sale_lib->get_customer(), $data, TRUE); - $data['invoice_number'] = $this->_substitute_invoice_number($customer_info); - $data['invoice_number_enabled'] = $this->sale_lib->is_invoice_number_enabled(); + $invoice_format = $this->config->item('sales_invoice_format'); + $data['invoice_format'] = $invoice_format; + + $this->set_invoice_number($invoice_format); + $data['invoice_number'] = $invoice_format; + + $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') + { + $data['mode_label'] = $this->lang->line('sales_invoice'); + } + elseif($this->sale_lib->get_mode() == 'sale_quote') + { + $data['mode_label'] = $this->lang->line('sales_quote'); + } + else + { + $data['mode_label'] = $this->lang->line('sales_receipt'); + } $data = $this->xss_clean($data); $this->load->view("sales/register", $data); @@ -700,18 +870,14 @@ class Sales extends Secure_Controller public function receipt($sale_id) { $data = $this->_load_sale_data($sale_id); - $this->load->view('sales/receipt', $data); - $this->sale_lib->clear_all(); } public function invoice($sale_id) { $data = $this->_load_sale_data($sale_id); - $this->load->view('sales/invoice', $data); - $this->sale_lib->clear_all(); } @@ -722,33 +888,31 @@ class Sales extends Secure_Controller $data['employees'] = array(); foreach($this->Employee->get_all()->result() as $employee) { - foreach(get_object_vars($employee) as $property => $value) + foreach (get_object_vars($employee) as $property => $value) { $employee->$property = $this->xss_clean($value); } - + $data['employees'][$employee->person_id] = $employee->first_name . ' ' . $employee->last_name; } - $sale_info = $this->xss_clean($this->Sale->get_info($sale_id)->row_array()); + $sale_info = $this->xss_clean($this->Sale->get_info($sale_id)->row_array()); $data['selected_customer_name'] = $sale_info['customer_name']; $data['selected_customer_id'] = $sale_info['customer_id']; $data['sale_info'] = $sale_info; $data['payments'] = array(); - foreach($this->Sale->get_sale_payments($sale_id)->result() as $payment) + foreach ($this->Sale->get_sale_payments($sale_id)->result() as $payment) { - foreach(get_object_vars($payment) as $property => $value) + foreach (get_object_vars($payment) as $property => $value) { $payment->$property = $this->xss_clean($value); } - $data['payments'][] = $payment; } - + // don't allow gift card to be a payment option in a sale transaction edit because it's a complex change $data['payment_options'] = $this->xss_clean($this->Sale->get_payment_options(FALSE)); - $this->load->view('sales/form', $data); } @@ -757,10 +921,10 @@ class Sales extends Secure_Controller $employee_id = $this->Employee->get_logged_in_employee_info()->person_id; $sale_ids = $sale_id == -1 ? $this->input->post('ids') : array($sale_id); - if($this->Sale->delete_list($sale_ids, $employee_id, $update_inventory)) + if ($this->Sale->delete_list($sale_ids, $employee_id, $update_inventory)) { echo json_encode(array('success' => TRUE, 'message' => $this->lang->line('sales_successfully_deleted') . ' ' . - count($sale_ids) . ' ' . $this->lang->line('sales_one_or_multiple'), 'ids' => $sale_ids)); + count($sale_ids) . ' ' . $this->lang->line('sales_one_or_multiple'), 'ids' => $sale_ids)); } else { @@ -771,9 +935,7 @@ class Sales extends Secure_Controller public function save($sale_id = -1) { $newdate = $this->input->post('date'); - $date_formatter = date_create_from_format($this->config->item('dateformat') . ' ' . $this->config->item('timeformat'), $newdate); - $sale_data = array( 'sale_time' => $date_formatter->format('Y-m-d H:i:s'), 'customer_id' => $this->input->post('customer_id') != '' ? $this->input->post('customer_id') : NULL, @@ -785,24 +947,27 @@ class Sales extends Secure_Controller // go through all the payment type input from the form, make sure the form matches the name and iterator number $payments = array(); $number_of_payments = $this->input->post('number_of_payments'); - for ($i = 0; $i < $number_of_payments; ++$i) + for($i = 0; $i < $number_of_payments; ++$i) { $payment_amount = $this->input->post('payment_amount_' . $i); $payment_type = $this->input->post('payment_type_' . $i); // remove any 0 payment if by mistake any was introduced at sale time - if($payment_amount != 0) + if ($payment_amount != 0) { // search for any payment of the same type that was already added, if that's the case add up the new payment amount $key = FALSE; - if(!empty($payments)) + if (!empty($payments)) { // search in the multi array the key of the entry containing the current payment_type // NOTE: in PHP5.5 the array_map could be replaced by an array_column - $key = array_search($payment_type, array_map(function($v){return $v['payment_type'];}, $payments)); + $key = array_search($payment_type, array_map(function ($v) + { + return $v['payment_type']; + }, $payments)); } // if no previous payment is found add a new one - if($key === FALSE) + if ($key === FALSE) { $payments[] = array('payment_type' => $payment_type, 'payment_amount' => $payment_amount); } @@ -814,7 +979,7 @@ class Sales extends Secure_Controller } } - if($this->Sale->update($sale_id, $sale_data, $payments)) + if ($this->Sale->update($sale_id, $sale_data, $payments)) { echo json_encode(array('success' => TRUE, 'message' => $this->lang->line('sales_successfully_updated'), 'id' => $sale_id)); } @@ -827,12 +992,45 @@ class Sales extends Secure_Controller public function cancel() { $this->sale_lib->clear_all(); + $this->_reload(); + } + public function discard_quote() + { + $suspended_id = $this->sale_lib->get_suspended_id(); + $this->sale_lib->clear_all(); + $this->Sale_suspended->delete($suspended_id); $this->_reload(); } public function suspend() - { + { + $cart = $this->sale_lib->get_cart(); + $payments = $this->sale_lib->get_payments(); + $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); + $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(); + + //SAVE sale to database + $data = array(); + if ($this->Sale_suspended->save($cart, $customer_id, $employee_id, $comment, $invoice_number, $quote_number, $payments) == '-1') + { + $data['error'] = $this->lang->line('sales_unsuccessfully_suspended_sale'); + } + else + { + $data['success'] = $this->lang->line('sales_successfully_suspended_sale'); + } + + $this->sale_lib->clear_all(); + $this->_reload($data); + } + + public function suspend_quote($quote_number) + { $cart = $this->sale_lib->get_cart(); $payments = $this->sale_lib->get_payments(); $employee_id = $this->Employee->get_logged_in_employee_info()->person_id; @@ -843,7 +1041,9 @@ class Sales extends Secure_Controller //SAVE sale to database $data = array(); - if($this->Sale_suspended->save($cart, $customer_id, $employee_id, $comment, $invoice_number, $payments) == '-1') + $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') { $data['error'] = $this->lang->line('sales_unsuccessfully_suspended_sale'); } @@ -851,37 +1051,29 @@ class Sales extends Secure_Controller { $data['success'] = $this->lang->line('sales_successfully_suspended_sale'); } - - $this->sale_lib->clear_all(); - - $this->_reload($data); } - + public function suspended() - { + { $data = array(); $data['suspended_sales'] = $this->xss_clean($this->Sale_suspended->get_all()->result_array()); - $this->load->view('sales/suspended', $data); } - + 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); - $this->_reload(); } - + public function check_invoice_number() { $sale_id = $this->input->post('sale_id'); $invoice_number = $this->input->post('invoice_number'); $exists = !empty($invoice_number) && $this->Sale->check_invoice_number_exists($invoice_number, $sale_id); - echo !$exists ? 'true' : 'false'; } @@ -890,11 +1082,11 @@ class Sales extends Secure_Controller $filteredCart = array(); foreach($cart as $id => $item) { - if ($item['print_option'] == '0') // always include + if($item['print_option'] == '0') // always include { $filteredCart[$id] = $item; } - elseif ($item['print_option'] == '1' && $item['price'] != 0) // include only if the price is not zero + elseif($item['print_option'] == '1' && $item['price'] != 0) // include only if the price is not zero { $filteredCart[$id] = $item; } @@ -903,4 +1095,5 @@ class Sales extends Secure_Controller return $filteredCart; } } -?> + +?> \ No newline at end of file diff --git a/application/language/en/config_lang.php b/application/language/en/config_lang.php index 352400f47..d8fda667f 100644 --- a/application/language/en/config_lang.php +++ b/application/language/en/config_lang.php @@ -101,6 +101,8 @@ $lang["config_invoice_email_message"] = "Invoice Email Template"; $lang["config_invoice_printer"] = "Invoice Printer"; $lang["config_jsprintsetup_required"] = "Warning! This disabled functionality will only work if you have the FireFox jsPrintSetup addon installed. Save anyway?"; $lang["config_language"] = "Language"; +$lang["config_last_used_invoice_number"] = "Last Used Invoice Number"; +$lang["config_last_used_quote_number"] = "Last Used Quote Number"; $lang["config_left"] = "Left"; $lang["config_license"] = "License"; $lang["config_license_configuration"] = "License Statement"; @@ -160,9 +162,11 @@ $lang["config_receipt_default"] = "Default"; $lang["config_receipt_short"] = "Short"; $lang["config_receiving_calculate_average_price"] = "Calc avg. Price (Receiving)"; $lang["config_recv_invoice_format"] = "Receivings Invoice Format"; +$lang["config_register_mode_default"] = "Default Register Mode"; $lang["config_return_policy_required"] = "Return policy is a required field"; $lang["config_right"] = "Right"; $lang["config_sales_invoice_format"] = "Sales Invoice Format"; +$lang["config_sales_quote_format"] = "Sales Quote Format"; $lang["config_saved_successfully"] = "Configuration saved successfully"; $lang["config_saved_unsuccessfully"] = "Configuration saved unsuccessfully"; $lang["config_statistics"] = "Send statistics"; diff --git a/application/language/en/sales_lang.php b/application/language/en/sales_lang.php index 70151efa5..fbb14625a 100644 --- a/application/language/en/sales_lang.php +++ b/application/language/en/sales_lang.php @@ -29,6 +29,7 @@ $lang["sales_delete_entire_sale"] = "Delete entire sale"; $lang["sales_delete_successful"] = "You have successfully deleted a sale"; $lang["sales_delete_unsuccessful"] = "You have unsuccessfully deleted a sale"; $lang["sales_description_abbrv"] = "Desc"; +$lang["sales_discard_quote"] = "Discard"; $lang["sales_discount"] = "Disc %"; $lang["sales_discount_included"] = "% Discount"; $lang["sales_discount"] = "Discount"; @@ -81,9 +82,13 @@ $lang["sales_payment_type"] = "Type"; $lang["sales_payments_total"] = "Payments Total"; $lang["sales_price"] = "Price"; $lang["sales_print_after_sale"] = "Print after sale"; +$lang["sales_quote_number"] = "Quote Number"; $lang["sales_quantity"] = "Qty."; $lang["sales_quantity_less_than_zero"] = "Warning, Desired Quantity is Insufficient. You can still process the sale, but check your inventory"; $lang["sales_quantity_less_than_reorder_level"] = "Warning, Desired Quantity is below reorder level"; +$lang["sales_quote"] = "Quote"; +$lang["sales_quote_sent"] = "Quote sent to"; +$lang["sales_quote_unsent"] = "Quote failed to be sent to"; $lang["sales_receipt"] = "Sales Receipt"; $lang["sales_receipt_number"] = "Sale #"; $lang["sales_receipt_sent"] = "Receipt sent to"; @@ -92,10 +97,12 @@ $lang["sales_register"] = "Sales Register"; $lang["sales_remove_customer"] = "Remove Customer"; $lang["sales_return"] = "Return"; $lang["sales_sale"] = "Sale"; +$lang["sales_sale_by_invoice"] = "Sale by Invoice"; $lang["sales_sale_for_customer"] = "Customer:"; $lang["sales_sale_time"] = "Time"; $lang["sales_select_customer"] = "Select Customer (Optional)"; $lang["sales_send_invoice"] = "Send Invoice"; +$lang["sales_send_quote"] = "Send Quote"; $lang["sales_send_receipt"] = "Send Receipt"; $lang["sales_serial"] = "Serial"; $lang["sales_show_invoice"] = "Show Invoice"; diff --git a/application/libraries/Sale_lib.php b/application/libraries/Sale_lib.php index 6422baae0..3b43657ab 100644 --- a/application/libraries/Sale_lib.php +++ b/application/libraries/Sale_lib.php @@ -18,6 +18,15 @@ class Sale_lib ); } + public function get_register_mode_options() + { + return array( + 'sale' => $this->CI->lang->line('sales_receipt'), + 'sale_invoice' => $this->CI->lang->line('sales_invoice'), + 'sale_quote' => $this->CI->lang->line('sales_quote') + ); + } + public function get_cart() { if(!$this->CI->session->userdata('sales_cart')) @@ -28,6 +37,67 @@ class Sale_lib return $this->CI->session->userdata('sales_cart'); } + public function sort_and_filter_cart($cart) + { + + if (empty($cart)) + { + return $cart; + } + + $filtered_cart = array(); + + foreach($cart as $k=>$v) { + if($v['print_option'] == '0') + { + $filtered_cart[] = $v; + } + } + + // Entry sequence (this will render kits in the expected sequence) + if($this->CI->config->item('line_sequence') == '0') + { + $sort = array(); + foreach($filtered_cart as $k=>$v) { + $sort['line'][$k] = $v['line']; + } + array_multisort($sort['line'], SORT_ASC, $filtered_cart); + } + // Group by Stock Type (nonstock first - type 1, stock next - type 0) + elseif($this->CI->config->item('line_sequence') == '1') + { + $sort = array(); + foreach($filtered_cart as $k=>$v) { + $sort['stock_type'][$k] = $v['stock_type']; + $sort['description'][$k] = $v['description']; + $sort['name'][$k] = $v['name']; + } + array_multisort($sort['stock_type'], SORT_DESC, $sort['description'], SORT_ASC, $sort['name'], SORT_ASC. $filtered_cart); + } + // Group by Item Category + elseif($this->CI->config->item('line_sequence') == '2') + { + $sort = array(); + foreach($filtered_cart as $k=>$v) { + $sort['category'][$k] = $v['stock_type']; + $sort['description'][$k] = $v['description']; + $sort['name'][$k] = $v['name']; + } + array_multisort($sort['category'], SORT_DESC, $sort['description'], SORT_ASC, $sort['name'], SORT_ASC, $filtered_cart); + } + // Group by entry sequence in descending sequence (the Standard) + else + { + $sort = array(); + foreach($filtered_cart as $k=>$v) { + $sort['line'][$k] = $v['line']; + } + array_multisort($sort['line'], SORT_ASC, $filtered_cart); + } + + return $filtered_cart; + } + public function set_cart($cart_data) { $this->CI->session->set_userdata('sales_cart', $cart_data); @@ -60,7 +130,12 @@ class Sale_lib { return $this->CI->session->userdata('sales_invoice_number'); } - + + public function get_quote_number() + { + return $this->CI->session->userdata('sales_quote_number'); + } + public function set_invoice_number($invoice_number, $keep_custom = FALSE) { $current_invoice_number = $this->CI->session->userdata('sales_invoice_number'); @@ -69,19 +144,54 @@ class Sale_lib $this->CI->session->set_userdata('sales_invoice_number', $invoice_number); } } - + + public function set_quote_number($quote_number, $keep_custom = FALSE) + { + $current_quote_number = $this->CI->session->userdata('sales_quote_number'); + if(!$keep_custom || empty($current_quote_number)) + { + $this->CI->session->set_userdata('sales_quote_number', $quote_number); + } + } + public function clear_invoice_number() { $this->CI->session->unset_userdata('sales_invoice_number'); } - - public function is_invoice_number_enabled() + + public function clear_quote_number() + { + $this->CI->session->unset_userdata('sales_quote_number'); + } + + public function set_suspended_id($suspended_id) + { + $this->CI->session->set_userdata('suspended_id', $suspended_id); + } + + public function get_suspended_id() + { + return $this->CI->session->userdata('suspended_id'); + } + + public function is_invoice_mode() { return ($this->CI->session->userdata('sales_invoice_number_enabled') == 'true' || - $this->CI->session->userdata('sales_invoice_number_enabled') == '1') && - $this->CI->config->item('invoice_enable') == TRUE; + $this->CI->session->userdata('sales_mode') == 'sale_invoice' || + ($this->CI->session->userdata('sales_invoice_number_enabled') == '1') && + $this->CI->config->item('invoice_enable') == TRUE); } - + + public function is_sale_by_receipt_mode() + { + return ($this->CI->session->userdata('sales_mode') == 'sale'); + } + + public function is_quote_mode() + { + return ($this->CI->session->userdata('sales_mode') == 'sale_quote'); + } + public function set_invoice_number_enabled($invoice_number_enabled) { return $this->CI->session->set_userdata('sales_invoice_number_enabled', $invoice_number_enabled); @@ -256,7 +366,13 @@ class Sale_lib { if(!$this->CI->session->userdata('sales_mode')) { - $this->set_mode('sale'); + if($this->CI->config->config['invoice_enable'] == '1') + { + $this->set_mode($this->CI->config->config['default_register_mode']); + } + else{ + $this->set_mode('sale'); + } } return $this->CI->session->userdata('sales_mode'); @@ -307,7 +423,7 @@ class Sale_lib $this->CI->session->unset_userdata('sales_giftcard_remainder'); } - public function add_item(&$item_id, $quantity = 1, $item_location, $discount = 0, $price = NULL, $description = NULL, $serialnumber = NULL, $include_deleted = FALSE, $print_option = '0') + public function add_item(&$item_id, $quantity = 1, $item_location, $discount = 0, $price = NULL, $description = NULL, $serialnumber = NULL, $include_deleted = FALSE, $print_option = '0', $stock_type = '0') { $item_info = $this->CI->Item->get_info_by_id_or_number($item_id); @@ -369,6 +485,20 @@ class Sale_lib $discount = 0.00; } + // For print purposes this simpifies line selection + // 0 will print, 2 will not print. The decision about 1 is made here + if($print_option =='1') + { + if($price == 0) + { + $print_option = '2'; + } + else + { + $print_option = '0'; + } + } + $total = $this->get_item_total($quantity, $price, $discount); $discounted_total = $this->get_item_total($quantity, $price, $discount, TRUE); //Item already exists and is not serialized, add to quantity @@ -391,7 +521,8 @@ class Sale_lib 'price' => $price, 'total' => $total, 'discounted_total' => $discounted_total, - 'print_option' => $print_option + 'print_option' => $print_option, + 'stock_type' => $stock_type ) ); //add to existing array @@ -417,7 +548,8 @@ class Sale_lib { $item_info = $this->CI->Item->get_info_by_id_or_number($item_id); - if ($item_info->stock_type == '0') { + if($item_info->stock_type == '0') + { $item_quantity = $this->CI->Item_quantity->get_item_quantity($item_id, $item_location)->quantity; $quantity_added = $this->get_quantity_already_added($item_id, $item_location); @@ -502,7 +634,7 @@ class Sale_lib foreach($this->CI->Sale->get_sale_items_ordered($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); + $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->print_option); } $this->set_customer($this->CI->Sale->get_customer($sale_id)->person_id); @@ -518,17 +650,17 @@ class Sale_lib foreach($this->CI->Item_kit_items->get_info($item_kit_id) as $item_kit_item) { - if ($price_option == '0') // all + if($price_option == '0') // all { $price = null; } - elseif ($price_option == '1') // item kit only + elseif($price_option == '1') // item kit only { $price = 0; } - elseif ($price_option == '2') // item kit plus stock items (assuming materials) + elseif($price_option == '2') // item kit plus stock items (assuming materials) { - if ($item_kit_item['stock_type'] == 0) // stock item + if($item_kit_item['stock_type'] == 0) // stock item { $price = null; } @@ -538,22 +670,23 @@ class Sale_lib } } - if ($kit_print_option == '0') // all + if($kit_print_option == '0') // all { $print_option = '0'; // print always } - elseif ($kit_print_option == '1') // priced + elseif($kit_print_option == '1') // priced { $print_option = '1'; // print if price not zero } - elseif ($kit_print_option == '2') // kit only if price is not zero + elseif($kit_print_option == '2') // kit only if price is not zero { $print_option = '2'; // Do not include in list } - $result &= $this->add_item($item_kit_item['item_id'], $item_kit_item['quantity'], $item_location, $discount, $price, null, null, null, $print_option); + $result &= $this->add_item($item_kit_item['item_id'], $item_kit_item['quantity'], $item_location, $discount, $price, null, null, null, $print_option, $print_option, $item_kit_item['stock_type']); - if ($stock_warning == null) { + if($stock_warning == null) + { $stock_warning = $this->out_of_stock($item_kit_item['item_id'], $item_location); } } @@ -586,7 +719,7 @@ class Sale_lib foreach($this->CI->Sale->get_sale_items_ordered($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->description, $row->serialnumber, TRUE, $row->print_option, $row->stock_type); } return $this->CI->session->userdata('sales_cart'); @@ -600,7 +733,7 @@ class Sale_lib 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, - $row->description, $row->serialnumber, TRUE, $row->print_option); + $row->description, $row->serialnumber, TRUE, $row->print_option, $row->stock_type); } foreach($this->CI->Sale_suspended->get_sale_payments($sale_id)->result() as $row) @@ -611,17 +744,19 @@ class Sale_lib $suspended_sale_info = $this->CI->Sale_suspended->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); } public function clear_all() { $this->set_invoice_number_enabled(FALSE); - $this->clear_mode(); $this->empty_cart(); $this->clear_comment(); $this->clear_email_receipt(); $this->clear_invoice_number(); + $this->clear_quote_number(); $this->clear_giftcard_remainder(); $this->empty_payments(); $this->remove_customer(); diff --git a/application/libraries/Token_lib.php b/application/libraries/Token_lib.php new file mode 100644 index 000000000..b2c0ea6f7 --- /dev/null +++ b/application/libraries/Token_lib.php @@ -0,0 +1,108 @@ +CI =& get_instance(); + } + + /** + * Expands all of the tokens found in a given text string and returns the results. + */ + public function render($tokened_text) + { + + // Transform legacy "$" tokens to their brace token equivalent + if(strpos($tokened_text, '$') !== false) + { + $tokened_text = str_replace('$YCO', '{YCO}', $tokened_text); + $tokened_text = str_replace('$CO', '{CO}', $tokened_text); + $tokened_text = str_replace('$SCO', '{SCO}', $tokened_text); + $tokened_text = str_replace('$CU', '{CU}', $tokened_text); + } + + // Apply the transformation for the "%" tokens if any are used + if(strpos($tokened_text, '%') !== false) + { + $tokened_text = strftime($tokened_text); + } + + // Call scan to build an array of all of the tokens used in the text to be transformed + $token_tree = $this->scan($tokened_text); + + if(empty($token_tree)) + { + if(strpos($tokened_text, '%') !== false) + { + return strftime($tokened_text); + } + else + { + return $tokened_text; + } + } + + $token_values = array(); + $tokens_to_replace = array(); + $this->generate($token_tree, $tokens_to_replace, $token_values); + + return str_replace($tokens_to_replace, $token_values, $tokened_text); + } + + /** + * Parses out the all of the tokens enclosed in braces {} and subparses on the colon : character where supplied + */ + public function scan($text) + { + // Matches tokens with the following pattern: [$token:$length] + preg_match_all('/ + \{ # [ - pattern start + ([^\s\{\}:]+) # match $token not containing whitespace : { or } + (?: + : # : - separator + ([^\s\{\}:]+) # match $length not containing whitespace : { or } + )? + \} # ] - pattern end + /x', $text, $matches); + + $tokens = $matches[1]; + $lengths = $matches[2]; + + $token_tree = array(); + for($i = 0; $i < count($tokens); $i++) { + $token_tree[$tokens[$i]][$lengths[$i]] = $matches[0][$i]; + } + + return $token_tree; + } + + public function generate($used_tokens, &$tokens_to_replace, &$token_values) + { + foreach($used_tokens as $token_code => $token_info) + { + // Generate value here based on the key value + $token_value = (new Token())->replace($token_code); + + foreach($token_info as $length => $token_spec) + { + $tokens_to_replace[] = $token_spec; + if(!empty($length)) + { + $token_values[] = str_pad($token_value, $length, '0', STR_PAD_LEFT); + } + else + { + $token_values[] = $token_value; + } + } + } + return $token_values; + } +} + +?> \ No newline at end of file diff --git a/application/libraries/tokens/Token.php b/application/libraries/tokens/Token.php new file mode 100644 index 000000000..b11522aad --- /dev/null +++ b/application/libraries/tokens/Token.php @@ -0,0 +1,58 @@ +CI =& get_instance(); + } + + public function replace($token_id) + { + if($token_id == 'CU') + { + return (new Token_customer())->get_value(); + } + elseif($token_id == 'CO') + { + return (new Token_invoice_count())->get_value(); + } + elseif($token_id == 'ISEQ') + { + return (new Token_invoice_sequence())->get_value(); + } + elseif($token_id == 'ISEQ') + { + return (new Token_invoice_sequence())->get_value(); + } + elseif($token_id == 'QSEQ') + { + return (new Token_quote_sequence())->get_value(); + } + elseif($token_id == 'SCO') + { + return (new Token_suspended_invoice_count())->get_value(); + } + elseif($token_id == 'YCO') + { + return (new Token_year_invoice_count())->get_value(); + } + return ''; + } + + function get_value(){ + return ''; + } +} + +?> \ No newline at end of file diff --git a/application/libraries/tokens/Token_customer.php b/application/libraries/tokens/Token_customer.php new file mode 100644 index 000000000..99688ef4f --- /dev/null +++ b/application/libraries/tokens/Token_customer.php @@ -0,0 +1,31 @@ +CI =& get_instance(); + $this->CI->load->library('sale_lib'); + } + + public function get_value() + { + // substitute customer info + $customer_id = $this->CI->sale_lib->get_customer(); + if($customer_id != -1) + { + $customer_info = $this->CI->Customer->get_info($customer_id); + if($customer_info != '') + { + return trim($customer_info->first_name . ' ' . $customer_info->last_name); + } + } + + return ''; + + } + +} \ No newline at end of file diff --git a/application/libraries/tokens/Token_invoice_count.php b/application/libraries/tokens/Token_invoice_count.php new file mode 100644 index 000000000..c3b9ab1fe --- /dev/null +++ b/application/libraries/tokens/Token_invoice_count.php @@ -0,0 +1,18 @@ +CI =& get_instance(); + $this->CI->load->model('Sale'); + } + + public function get_value() + { + return $this->CI->Sale->get_invoice_count(); + } +} \ No newline at end of file diff --git a/application/libraries/tokens/Token_invoice_sequence.php b/application/libraries/tokens/Token_invoice_sequence.php new file mode 100644 index 000000000..cdae5cdbb --- /dev/null +++ b/application/libraries/tokens/Token_invoice_sequence.php @@ -0,0 +1,17 @@ +CI =& get_instance(); + } + + public function get_value() + { + return $this->CI->Appconfig->acquire_save_next_invoice_sequence(); + } +} \ No newline at end of file diff --git a/application/libraries/tokens/Token_quote_sequence.php b/application/libraries/tokens/Token_quote_sequence.php new file mode 100644 index 000000000..7169ac695 --- /dev/null +++ b/application/libraries/tokens/Token_quote_sequence.php @@ -0,0 +1,17 @@ +CI =& get_instance(); + } + + public function get_value() + { + return $this->CI->Appconfig->acquire_save_next_quote_sequence(); + } +} \ 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 new file mode 100644 index 000000000..5b317943e --- /dev/null +++ b/application/libraries/tokens/Token_suspended_invoice_count.php @@ -0,0 +1,20 @@ +CI =& get_instance(); + $this->CI->load->model('Sale_suspended'); + } + + public function get_value() + { + return $this->CI->Sale_suspended->get_invoice_count(); + } +} + +?> \ No newline at end of file diff --git a/application/libraries/tokens/Token_year_invoice_count.php b/application/libraries/tokens/Token_year_invoice_count.php new file mode 100644 index 000000000..36a1e48eb --- /dev/null +++ b/application/libraries/tokens/Token_year_invoice_count.php @@ -0,0 +1,18 @@ +CI =& get_instance(); + $this->CI->load->model('Sale'); + } + + public function get_value() + { + return $this->CI->Sale->get_invoice_number_for_year(); + } +} \ No newline at end of file diff --git a/application/models/Appconfig.php b/application/models/Appconfig.php index 61dcb3fe4..60a89ad1f 100644 --- a/application/models/Appconfig.php +++ b/application/models/Appconfig.php @@ -74,5 +74,19 @@ class Appconfig extends CI_Model { return $this->db->empty_table('app_config'); } + + public function acquire_save_next_invoice_sequence() + { + $last_used = $this->get('last_used_invoice_number') + 1; + $this->save('last_used_invoice_number', $last_used); + return $last_used; + } + + public function acquire_save_next_quote_sequence() + { + $last_used = $this->get('last_used_quote_number') + 1; + $this->save('last_used_quote_number', $last_used); + return $last_used; + } } ?> \ No newline at end of file diff --git a/application/models/Sale.php b/application/models/Sale.php index 1d874121e..aa1fafae4 100644 --- a/application/models/Sale.php +++ b/application/models/Sale.php @@ -682,7 +682,8 @@ class Sale extends CI_Model print_option, items.name as name, category, - item_type'); + item_type, + stock_type'); $this->db->from('sales_items as sales_items'); $this->db->join('items as items', 'sales_items.item_id = items.item_id'); $this->db->where('sale_id', $sale_id); @@ -772,6 +773,19 @@ class Sale extends CI_Model return $this->Employee->get_info($this->db->get()->row()->employee_id); } + // 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); + if(!empty($sale_id)) + { + $this->db->where('sale_id !=', $sale_id); + } + + return ($this->db->get()->num_rows() == 1); + } + public function check_invoice_number_exists($invoice_number, $sale_id = '') { $this->db->from('sales'); diff --git a/application/models/Sale_suspended.php b/application/models/Sale_suspended.php index 2f7d448e3..fdce3e9a0 100644 --- a/application/models/Sale_suspended.php +++ b/application/models/Sale_suspended.php @@ -52,7 +52,7 @@ class Sale_suspended extends CI_Model return $this->db->update('sales_suspended', $sale_data); } - public function save($items, $customer_id, $employee_id, $comment, $invoice_number, $payments, $sale_id = FALSE) + public function save($items, $customer_id, $employee_id, $comment, $invoice_number, $quote_number, $payments, $sale_id = FALSE) { if(count($items) == 0) { @@ -64,7 +64,8 @@ class Sale_suspended extends CI_Model 'customer_id' => $this->Customer->exists($customer_id) ? $customer_id : null, 'employee_id' => $employee_id, 'comment' => $comment, - 'invoice_number' => $invoice_number + 'invoice_number' => $invoice_number, + 'quote_number' => $quote_number, ); //Run these queries as a transaction, we want to make sure we do all or nothing diff --git a/application/views/configs/invoice_config.php b/application/views/configs/invoice_config.php index adf8b09dd..e0ffef81a 100644 --- a/application/views/configs/invoice_config.php +++ b/application/views/configs/invoice_config.php @@ -14,8 +14,15 @@ 'checked' => $this->config->item('invoice_enable')));?> - -
+ +
+ lang->line('config_register_mode_default'), 'default_register_mode', array('class' => 'control-label col-xs-2')); ?> +
+ config->item('default_register_mode'), array('class' => 'form-control input-sm')); ?> +
+
+ +
lang->line('config_sales_invoice_format'), 'sales_invoice_format', array('class' => 'control-label col-xs-2')); ?>
-
+
+ lang->line('config_sales_quote_format'), 'sales_quote_format', array('class' => 'control-label col-xs-2')); ?> +
+ 'sales_quote_format', + 'id' => 'sales_quote_format', + 'class' => 'form-control input-sm', + 'value' => $this->config->item('sales_quote_format'))); ?> +
+
+ +
lang->line('config_recv_invoice_format'), 'recv_invoice_format', array('class' => 'control-label col-xs-2')); ?>
+
+ lang->line('config_last_used_invoice_number'), 'last_used_invoice_number', array('class' => 'control-label col-xs-2')); ?> +
+ 'number', + 'name' => 'last_used_invoice_number', + 'id' => 'last_used_invoice_number', + 'class' => 'form-control input-sm required', + 'value'=>$this->config->item('last_used_invoice_number'))); ?> +
+
+ +
+ lang->line('config_last_used_quote_number'), 'last_used_quote_number', array('class' => 'control-label col-xs-2')); ?> +
+ 'number', + 'name' => 'last_used_quote_number', + 'id' => 'last_used_quote_number', + 'class' => 'form-control input-sm required', + 'value'=>$this->config->item('last_used_quote_number'))); ?> +
+
+ 'submit_form', 'id' => 'submit_form', @@ -81,7 +123,7 @@ $(document).ready(function() { var enable_disable_invoice_enable = (function() { var invoice_enable = $("#invoice_enable").is(":checked"); - $("#sales_invoice_format, #recv_invoice_format, #invoice_default_comments, #invoice_email_message").prop("disabled", !invoice_enable); + $("#sales_invoice_format, #recv_invoice_format, #invoice_default_comments, #invoice_email_message, select[name='default_register_mode'], #sales_quote_format, select[name='line_sequence'], #last_used_invoice_number, #last_used_quote_number").prop("disabled", !invoice_enable); return arguments.callee; })(); @@ -91,7 +133,7 @@ $(document).ready(function() submitHandler: function(form) { $(form).ajaxSubmit({ beforeSerialize: function(arr, $form, options) { - $("#sales_invoice_format, #recv_invoice_format, #invoice_default_comments, #invoice_email_message").prop("disabled", false); + $("#sales_invoice_format, #sales_quote_format, #recv_invoice_format, #invoice_default_comments, #invoice_email_message, #last_used_invoice_number, #last_used_quote_number").prop("disabled", false); return true; }, success: function(response) { diff --git a/application/views/sales/quote.php b/application/views/sales/quote.php new file mode 100644 index 000000000..a8d80cc21 --- /dev/null +++ b/application/views/sales/quote.php @@ -0,0 +1,177 @@ +load->view("partial/header"); ?> + +".$error_message."
"; + exit; +} +?> + + + + + +load->view('partial/print_receipt', array('print_after_sale'=>$print_after_sale, 'selected_printer'=>'invoice_printer')); ?> + + + +
+ +
+
+ + + +
+ + +
+ +
+ + + + + + + + + + + + + + +
lang->line('sales_quote_number');?>
lang->line('common_date'); ?>
lang->line('sales_amount_due'); ?>
+
+ + + + + + + + + + + $item) + { + ?> + + + + + + + + + + + + + + + + + + + + + + + $value) + { + ?> + + + + + + + + + + + +
lang->line('sales_item_number'); ?>lang->line('sales_item_name'); ?>lang->line('sales_quantity'); ?>lang->line('sales_price'); ?>lang->line('sales_discount'); ?>lang->line('sales_total'); ?>
+
+ + + +load->view("partial/footer"); ?> diff --git a/application/views/sales/quote_email.php b/application/views/sales/quote_email.php new file mode 100644 index 000000000..d8b28f0d9 --- /dev/null +++ b/application/views/sales/quote_email.php @@ -0,0 +1,128 @@ + + + + + + + + +".$error_message."
"; + exit; +} +?> + +
+ + + + + + + + + + +
+
+
+
config->item('company'); ?>
+
+
+ + + + + + + + + + 0) + { + ?> + + + + + +
lang->line('sales_invoice_number');?>
lang->line('common_date'); ?>
lang->line('sales_amount_due'); ?>
+
+ + + + + + + + + + + + $item) + { + ?> + + + + + + + + + + + + + + + + + + + $value) { ?> + + + + + + + + + + + +
lang->line('sales_item_number'); ?>lang->line('sales_item_name'); ?>lang->line('sales_quantity'); ?>lang->line('sales_price'); ?>lang->line('sales_discount'); ?>lang->line('sales_total'); ?>
lang->line('sales_sub_total'); ?>
lang->line('sales_total'); ?>
+ +
+
+
+
config->item('payment_message')); ?>
+
lang->line('sales_comments'). ': ' . (empty($comments) ? $this->config->item('invoice_default_comments') : $comments); ?>
+
+ config->item('return_policy')); ?> +
+
+
+ +
+
+
+ + + diff --git a/application/views/sales/register.php b/application/views/sales/register.php index 4ab4b31db..3be717eb8 100644 --- a/application/views/sales/register.php +++ b/application/views/sales/register.php @@ -128,7 +128,7 @@ if (isset($success)) ');?> -
+
@@ -371,9 +371,16 @@ if (isset($success)) - -
 lang->line('sales_complete_sale'); ?>
- +
 lang->line('sales_complete_sale'); ?>
+ + 'buttons_form')); ?>
 lang->line('sales_suspend_sale'); ?>
+ +
 
+
 lang->line('sales_cancel_sale'); ?>
@@ -444,10 +460,10 @@ if (isset($success)) + // Only show this part if the payment cover the total + if($payments_cover_total || $quote_or_invoice_mode) + { + ?>
@@ -483,11 +499,12 @@ if (isset($success))
config->item('invoice_enable') == TRUE) + if (($mode == "sale") && $this->config->item('invoice_enable') == TRUE) { ?>
+
+
lang->line('items_stock_type'), 'stock_type', !empty($basic_version) ? array('class'=>'required control-label col-xs-3') : array('class'=>'control-label col-xs-3')); ?>
@@ -93,8 +94,9 @@
+ -
+
lang->line('items_supplier'), 'supplier', array('class'=>'control-label col-xs-3')); ?>
'form-control')); ?> diff --git a/database/3.0.2_to_3.1.0.sql b/database/3.0.2_to_3.1.0.sql index d42c2ff22..8529c5b18 100644 --- a/database/3.0.2_to_3.1.0.sql +++ b/database/3.0.2_to_3.1.0.sql @@ -25,7 +25,7 @@ ALTER TABLE `ospos_sales_suspended_items` INSERT INTO `ospos_app_config` (`key`, `value`) VALUES ('date_or_time_format', ''), -('sales_quote_format', 'Q%m{QSEQ:6}'), +('sales_quote_format', 'Q%y{QSEQ:6}'), ('default_register_mode', 'sale'), ('last_used_invoice_number', '0'), ('last_used_quote_number', '0'), diff --git a/database/database.sql b/database/database.sql index d928c3fb4..2aa0546eb 100644 --- a/database/database.sql +++ b/database/database.sql @@ -50,7 +50,7 @@ INSERT INTO `ospos_app_config` (`key`, `value`) VALUES ('line_sequence', '0'), ('recv_invoice_format', '$CO'), ('sales_invoice_format', '$CO'), -('sales_quote_format', 'Q%m{QSEQ:6}'), +('sales_quote_format', 'Q%y{QSEQ:6}'), ('invoice_email_message', 'Dear $CU, In attachment the receipt for sale $INV'), ('invoice_default_comments', 'This is a default comment'), ('print_silently', '1'), diff --git a/database/tables.sql b/database/tables.sql index f7f67c390..fc88d83bb 100644 --- a/database/tables.sql +++ b/database/tables.sql @@ -50,7 +50,7 @@ INSERT INTO `ospos_app_config` (`key`, `value`) VALUES ('line_sequence', '0'), ('recv_invoice_format', '$CO'), ('sales_invoice_format', '$CO'), -('sales_quote_format', 'Q%m{QSEQ:6}'), +('sales_quote_format', 'Q%y{QSEQ:6}'), ('invoice_email_message', 'Dear $CU, In attachment the receipt for sale $INV'), ('invoice_default_comments', 'This is a default comment'), ('print_silently', '1'),