diff --git a/application/config/constants.php b/application/config/constants.php index c16578918..140987273 100644 --- a/application/config/constants.php +++ b/application/config/constants.php @@ -110,6 +110,7 @@ define('ITEM', 0); define('ITEM_KIT', 1); define('ITEM_AMOUNT_ENTRY', 2); define('ITEM_TEMP', 3); +define('NEW_ITEM', -1); define('PRINT_ALL', 0); define('PRINT_PRICED', 1); diff --git a/application/controllers/Attributes.php b/application/controllers/Attributes.php index 3527dafd8..8a07a32f0 100644 --- a/application/controllers/Attributes.php +++ b/application/controllers/Attributes.php @@ -1,6 +1,6 @@ Attribute->search($search, $limit, $offset, $sort, $order); $total_rows = $this->Attribute->get_found_rows($search); - $data_rows = array(); + $data_rows = []; foreach($attributes->result() as $attribute) { - $attribute->definition_flags = $this->_get_attributes($attribute->definition_flags); + $attribute->definition_flags = $this->get_attributes($attribute->definition_flags); $data_rows[] = get_attribute_definition_data_row($attribute, $this); } @@ -68,7 +68,7 @@ class Attributes extends Secure_Controller { $definition_flags = 0; - $flags = (empty($this->input->post('definition_flags'))) ? array() : $this->input->post('definition_flags'); + $flags = (empty($this->input->post('definition_flags'))) ? [] : $this->input->post('definition_flags'); foreach($flags as $flag) { @@ -129,15 +129,15 @@ class Attributes extends Secure_Controller public function get_row($row_id) { $attribute_definition_info = $this->Attribute->get_info($row_id); - $attribute_definition_info->definition_flags = $this->_get_attributes($attribute_definition_info->definition_flags); + $attribute_definition_info->definition_flags = $this->get_attributes($attribute_definition_info->definition_flags); $data_row = $this->xss_clean(get_attribute_definition_data_row($attribute_definition_info)); echo json_encode($data_row); } - private function _get_attributes($definition_flags = 0) + private function get_attributes($definition_flags = 0) { - $definition_flag_names = array(); + $definition_flag_names = []; foreach (Attribute::get_definition_flags() as $id => $term) { if ($id & $definition_flags) @@ -163,11 +163,11 @@ class Attributes extends Secure_Controller $data['definition_info'] = $info; $show_all = Attribute::SHOW_IN_ITEMS | Attribute::SHOW_IN_RECEIVINGS | Attribute::SHOW_IN_SALES; - $data['definition_flags'] = $this->_get_attributes($show_all); + $data['definition_flags'] = $this->get_attributes($show_all); $selected_flags = $info->definition_flags === '' ? $show_all : $info->definition_flags; - $data['selected_definition_flags'] = $this->_get_attributes($selected_flags); + $data['selected_definition_flags'] = $this->get_attributes($selected_flags); - $this->load->view("attributes/form", $data); + $this->load->view('attributes/form', $data); } public function delete_value($attribute_id) diff --git a/application/controllers/Items.php b/application/controllers/Items.php index a3ca4dda5..2075c6230 100644 --- a/application/controllers/Items.php +++ b/application/controllers/Items.php @@ -1,6 +1,6 @@ xss_clean($this->item_lib->get_item_location()); $data['stock_locations'] = $this->xss_clean($this->Stock_location->get_allowed_locations()); - //Filters that will be loaded in the multiselect dropdown - $data['filters'] = array('empty_upc' => $this->lang->line('items_empty_upc_items'), + //Filters that will be loaded in the multiselect dropdown + $data['filters'] = array( + 'empty_upc' => $this->lang->line('items_empty_upc_items'), 'low_inventory' => $this->lang->line('items_low_inventory_items'), 'is_serialized' => $this->lang->line('items_serialized_items'), 'no_description' => $this->lang->line('items_no_description_items'), @@ -32,7 +33,7 @@ class Items extends Secure_Controller } /* - Returns Items table data rows. This will be called with AJAX. + * Returns Items table data rows. This will be called with AJAX. */ public function search() { @@ -46,7 +47,8 @@ class Items extends Secure_Controller $definition_names = $this->Attribute->get_definitions_by_flags(Attribute::SHOW_IN_ITEMS); - $filters = array('start_date' => $this->input->get('start_date'), + $filters = array( + 'start_date' => $this->input->get('start_date'), 'end_date' => $this->input->get('end_date'), 'stock_location_id' => $this->item_lib->get_item_location(), 'empty_upc' => FALSE, @@ -58,21 +60,20 @@ class Items extends Secure_Controller 'temporary' => FALSE, 'definition_ids' => array_keys($definition_names)); - //Check if any filter is set in the multiselect dropdown + //Check if any filter is set in the multiselect dropdown $filledup = array_fill_keys($this->input->get('filters'), TRUE); $filters = array_merge($filters, $filledup); - $items = $this->Item->search($search, $filters, $limit, $offset, $sort, $order); - $total_rows = $this->Item->get_found_rows($search, $filters); + $data_rows = []; - $data_rows = array(); foreach($items->result() as $item) { $data_rows[] = $this->xss_clean(get_item_data_row($item)); - if($item->pic_filename!='') + + if($item->pic_filename !== NULL) { - $this->_update_pic_filename($item); + $this->update_pic_filename($item); } } @@ -84,18 +85,17 @@ class Items extends Secure_Controller $this->load->helper('file'); $this->load->library('image_lib'); - //In this context, $pic_filename always has .ext - $ext = pathinfo($pic_filename, PATHINFO_EXTENSION); + $file_extension = pathinfo($pic_filename, PATHINFO_EXTENSION); $images = glob('./uploads/item_pics/' . $pic_filename); - //Make sure we pick only the file name, without extension $base_path = './uploads/item_pics/' . pathinfo($pic_filename, PATHINFO_FILENAME); + if(sizeof($images) > 0) { $image_path = $images[0]; - $thumb_path = $base_path . $this->image_lib->thumb_marker . '.' . $ext; + $thumb_path = $base_path . $this->image_lib->thumb_marker . '.' . $file_extension; - if(sizeof($images) < 2) + if(sizeof($images) < 2 && !file_exists($thumb_path)) { $config['image_library'] = 'gd2'; $config['source_image'] = $image_path; @@ -103,8 +103,10 @@ class Items extends Secure_Controller $config['create_thumb'] = TRUE; $config['width'] = 52; $config['height'] = 32; + $this->image_lib->initialize($config); - $image = $this->image_lib->resize(); + $this->image_lib->resize(); + $thumb_path = $this->image_lib->full_dst_path; } $this->output->set_content_type(get_mime_by_extension($thumb_path)); @@ -117,8 +119,10 @@ class Items extends Secure_Controller */ public function suggest_search() { - $suggestions = $this->xss_clean($this->Item->get_search_suggestions($this->input->post_get('term'), - array('search_custom' => $this->input->post('search_custom'), 'is_deleted' => $this->input->post('is_deleted') != NULL), FALSE)); + $options = array( + 'search_custom' => $this->input->post('search_custom'), + 'is_deleted' => $this->input->post('is_deleted') !== NULL); + $suggestions = $this->xss_clean($this->Item->get_search_suggestions($this->input->post_get('term'), $options, FALSE)); echo json_encode($suggestions); } @@ -168,9 +172,10 @@ class Items extends Secure_Controller public function get_row($item_ids) { - $item_infos = $this->Item->get_multiple_info(explode(":", $item_ids), $this->item_lib->get_item_location()); + $item_infos = $this->Item->get_multiple_info(explode(':', $item_ids), $this->item_lib->get_item_location()); + + $result = []; - $result = array(); foreach($item_infos->result() as $item_info) { $result[$item_info->item_id] = $this->xss_clean(get_item_data_row($item_info)); @@ -179,14 +184,14 @@ class Items extends Secure_Controller echo json_encode($result); } - public function view($item_id = -1) + public function view($item_id = NEW_ITEM) { - if($item_id == -1) + if($item_id === NEW_ITEM) { - $data = array(); + $data = []; } - //allow_temp_items is set in the index function of items.php or sales.php + //allow_temp_items is set in the index function of items.php or sales.php $data['allow_temp_item'] = $this->session->userdata('allow_temp_items'); $data['item_tax_info'] = $this->xss_clean($this->Item_taxes->get_info($item_id)); $data['default_tax_1_rate'] = ''; @@ -207,9 +212,9 @@ class Items extends Secure_Controller $item_info->$property = $this->xss_clean($value); } - if($data['allow_temp_item'] == 1) + if($data['allow_temp_item'] === 1) { - if($item_id != -1) + if($item_id !== NEW_ITEM) { if($item_info->item_type != ITEM_TEMP) { @@ -226,20 +231,20 @@ class Items extends Secure_Controller } $use_destination_based_tax = (boolean)$this->config->item('use_destination_based_tax'); - $data['include_hsn'] = $this->config->item('include_hsn') == '1'; + $data['include_hsn'] = $this->config->item('include_hsn') === '1'; $data['category_dropdown'] = $this->config->item('category_dropdown'); - if($data['category_dropdown'] == 1) + if($data['category_dropdown'] === '1') { $categories = array('' => $this->lang->line('items_none')); - $category_options = $this->Attribute->get_definition_values(-1); + $category_options = $this->Attribute->get_definition_values(CATEGORY_DEFINITION_ID); $category_options = array_combine($category_options,$category_options); //Overwrite indexes with values for saving in items table instead of attributes $data['categories'] = array_merge($categories,$category_options); $data['selected_category'] = $item_info->category; } - if($item_id == -1) + if($item_id === NEW_ITEM) { $data['default_tax_1_rate'] = $this->config->item('default_tax_1_rate'); $data['default_tax_2_rate'] = $this->config->item('default_tax_2_rate'); @@ -260,9 +265,11 @@ class Items extends Secure_Controller } } - $data['standard_item_locked'] = ($data['item_kit_disabled'] && $item_info->item_type == ITEM_KIT + $data['standard_item_locked'] = ( + $data['item_kit_disabled'] + && $item_info->item_type == ITEM_KIT && !$data['allow_temp_item'] - & !($this->config->item('derive_sale_quantity') == '1')); + && !($this->config->item('derive_sale_quantity') === '1')); $data['item_info'] = $item_info; @@ -288,17 +295,21 @@ class Items extends Secure_Controller if($use_destination_based_tax) { $data['use_destination_based_tax'] = TRUE; - $tax_categories = array(); + $tax_categories = []; + foreach($this->Tax_category->get_all()->result_array() as $row) { $tax_categories[$this->xss_clean($row['tax_category_id'])] = $this->xss_clean($row['tax_category']); } - $tax_category = ""; - if ($item_info->tax_category_id != NULL) + + $tax_category = ''; + + if ($item_info->tax_category_id !== NULL) { $tax_category_info=$this->Tax_category->get_info($item_info->tax_category_id); $tax_category= $tax_category_info->tax_category; } + $data['tax_categories'] = $tax_categories; $data['tax_category'] = $tax_category; $data['tax_category_id'] = $item_info->tax_category_id; @@ -306,22 +317,20 @@ class Items extends Secure_Controller else { $data['use_destination_based_tax'] = FALSE; - $data['tax_categories'] = array(); + $data['tax_categories'] = []; $data['tax_category'] = ''; } - $data['logo_exists'] = $item_info->pic_filename != ''; - $ext = pathinfo($item_info->pic_filename, PATHINFO_EXTENSION); + $data['logo_exists'] = $item_info->pic_filename !== ''; + $file_extension = pathinfo($item_info->pic_filename, PATHINFO_EXTENSION); - if($ext == '') + if(empty($file_extension)) { - //If file extension is not found guess it (legacy) - $images = glob('./uploads/item_pics/' . $item_info->pic_filename . '.*'); + $images = glob("./uploads/item_pics/$item_info->pic_filename.*"); } else { - //Else just pick that file - $images = glob('./uploads/item_pics/' . $item_info->pic_filename); + $images = glob("./uploads/item_pics/$item_info->pic_filename"); } $data['image_path'] = sizeof($images) > 0 ? base_url($images[0]) : ''; @@ -332,14 +341,14 @@ class Items extends Secure_Controller $location = $this->xss_clean($location); $quantity = $this->xss_clean($this->Item_quantity->get_item_quantity($item_id, $location['location_id'])->quantity); - $quantity = ($item_id == -1) ? 0 : $quantity; + $quantity = ($item_id === NEW_ITEM) ? 0 : $quantity; $location_array[$location['location_id']] = array('location_name' => $location['location_name'], 'quantity' => $quantity); $data['stock_locations'] = $location_array; } $data['selected_low_sell_item_id'] = $item_info->low_sell_item_id; - if($item_id != -1 && $item_info->item_id != $item_info->low_sell_item_id) + if($item_id !== NEW_ITEM && $item_info->item_id !== $item_info->low_sell_item_id) { $low_sell_item_info = $this->Item->get_info($item_info->low_sell_item_id); $data['selected_low_sell_item'] = implode(NAME_SEPARATOR, array($low_sell_item_info->name, $low_sell_item_info->pack_name)); @@ -352,17 +361,19 @@ class Items extends Secure_Controller $this->load->view('items/form', $data); } - public function inventory($item_id = -1) + public function inventory($item_id = NEW_ITEM) { $item_info = $this->Item->get_info($item_id); + foreach(get_object_vars($item_info) as $property => $value) { $item_info->$property = $this->xss_clean($value); } - $data['item_info'] = $item_info; - $data['stock_locations'] = array(); + $data['item_info'] = $item_info; + $data['stock_locations'] = []; $stock_locations = $this->Stock_location->get_undeleted_all()->result_array(); + foreach($stock_locations as $location) { $location = $this->xss_clean($location); @@ -375,17 +386,19 @@ class Items extends Secure_Controller $this->load->view('items/form_inventory', $data); } - public function count_details($item_id = -1) + public function count_details($item_id = NEW_ITEM) { $item_info = $this->Item->get_info($item_id); + foreach(get_object_vars($item_info) as $property => $value) { $item_info->$property = $this->xss_clean($value); } - $data['item_info'] = $item_info; - $data['stock_locations'] = array(); + $data['item_info'] = $item_info; + $data['stock_locations'] = []; $stock_locations = $this->Stock_location->get_undeleted_all()->result_array(); + foreach($stock_locations as $location) { $location = $this->xss_clean($location); @@ -408,29 +421,25 @@ class Items extends Secure_Controller $data['barcode_config'] = $config; - //Check the list of items to see if any item_number field is empty foreach($result as &$item) { $item = $this->xss_clean($item); - //Update the barcode field if empty / NULL with the newly generated barcode if(empty($item['item_number']) && $this->config->item('barcode_generate_if_empty')) { - // get the newly generated barcode $barcode_instance = Barcode_lib::barcode_instance($item, $config); $item['item_number'] = $barcode_instance->getData(); - $save_item = array('item_number' => $item['item_number']); - $this->Item->save($save_item, $item['item_id']); // update the item in the database in order to save the barcode field + $this->Item->save($save_item, $item['item_id']); } } $data['items'] = $result; - $this->load->view('barcodes/barcode_sheet', $data); // display barcodes + $this->load->view('barcodes/barcode_sheet', $data); } - public function attributes($item_id) + public function attributes($item_id = NEW_ITEM) { $data['item_id'] = $item_id; $definition_ids = json_decode($this->input->post('definition_ids'), TRUE); @@ -446,7 +455,7 @@ class Items extends Secure_Controller $values['attribute_value'] = $attribute_value; $values['selected_value'] = ''; - if ($definition_value['definition_type'] == DROPDOWN) + if ($definition_value['definition_type'] === DROPDOWN) { $values['values'] = $this->Attribute->get_definition_values($definition_id); $link_value = $this->Attribute->get_link_value($item_id, $definition_id); @@ -467,12 +476,13 @@ class Items extends Secure_Controller public function bulk_edit() { $suppliers = array('' => $this->lang->line('items_none')); + foreach($this->Supplier->get_all()->result_array() as $row) { $row = $this->xss_clean($row); - $suppliers[$row['person_id']] = $row['company_name']; } + $data['suppliers'] = $suppliers; $data['allow_alt_description_choices'] = array( '' => $this->lang->line('items_do_nothing'), @@ -487,18 +497,18 @@ class Items extends Secure_Controller $this->load->view('items/form_bulk', $data); } - public function save($item_id = -1) + public function save($item_id = NEW_ITEM) { - $upload_success = $this->_handle_image_upload(); + $upload_success = $this->handle_image_upload(); $upload_data = $this->upload->data(); - $receiving_quantity = parse_quantity($this->input->post('receiving_quantity')); - $item_type = $this->input->post('item_type') == NULL ? ITEM : $this->input->post('item_type'); + $item_type = $this->input->post('item_type') === NULL ? ITEM : $this->input->post('item_type'); - if($receiving_quantity == '0' && $item_type!= ITEM_TEMP) + if($receiving_quantity === 0 && $item_type !== ITEM_TEMP) { - $receiving_quantity = '1'; + $receiving_quantity = 1; } + $default_pack_name = $this->lang->line('items_default_pack_name'); //Save item data @@ -507,20 +517,20 @@ class Items extends Secure_Controller 'description' => $this->input->post('description'), 'category' => $this->input->post('category'), 'item_type' => $item_type, - 'stock_type' => $this->input->post('stock_type') == NULL ? HAS_STOCK : $this->input->post('stock_type'), - 'supplier_id' => $this->input->post('supplier_id') == '' ? NULL : $this->input->post('supplier_id'), - 'item_number' => $this->input->post('item_number') == '' ? NULL : $this->input->post('item_number'), + 'stock_type' => $this->input->post('stock_type') === NULL ? HAS_STOCK : intval($this->input->post('stock_type')), + 'supplier_id' => empty($this->input->post('supplier_id')) ? NULL : intval($this->input->post('supplier_id')), + 'item_number' => empty($this->input->post('item_number')) ? NULL : $this->input->post('item_number'), 'cost_price' => parse_decimals($this->input->post('cost_price')), 'unit_price' => parse_decimals($this->input->post('unit_price')), 'reorder_level' => parse_quantity($this->input->post('reorder_level')), 'receiving_quantity' => $receiving_quantity, - 'allow_alt_description' => $this->input->post('allow_alt_description') != NULL, - 'is_serialized' => $this->input->post('is_serialized') != NULL, - 'qty_per_pack' => $this->input->post('qty_per_pack') == NULL ? 1 : $this->input->post('qty_per_pack'), - 'pack_name' => $this->input->post('pack_name') == NULL ? $default_pack_name : $this->input->post('pack_name'), - 'low_sell_item_id' => $this->input->post('low_sell_item_id') == NULL ? $item_id : $this->input->post('low_sell_item_id'), - 'deleted' => $this->input->post('is_deleted') != NULL, - 'hsn_code' => $this->input->post('hsn_code') == NULL ? '' : $this->input->post('hsn_code') + 'allow_alt_description' => $this->input->post('allow_alt_description') !== NULL, + 'is_serialized' => $this->input->post('is_serialized') !== NULL, + 'qty_per_pack' => $this->input->post('qty_per_pack') === NULL ? 1 : $this->input->post('qty_per_pack'), + 'pack_name' => $this->input->post('pack_name') === NULL ? $default_pack_name : $this->input->post('pack_name'), + 'low_sell_item_id' => $this->input->post('low_sell_item_id') === NULL ? $item_id : $this->input->post('low_sell_item_id'), + 'deleted' => $this->input->post('is_deleted') !== NULL, + 'hsn_code' => $this->input->post('hsn_code') === NULL ? '' : $this->input->post('hsn_code') ); if($item_data['item_type'] == ITEM_TEMP) @@ -530,23 +540,22 @@ class Items extends Secure_Controller $item_data['reorder_level'] = 0; } - $x = $this->input->post('tax_category_id'); + $tax_category_id = $this->input->post('tax_category_id'); - if(!isset($x)) + if(!isset($tax_category_id)) { $item_data['tax_category_id'] = ''; } else { - $item_data['tax_category_id'] = $this->input->post('tax_category_id') == '' ? NULL : $this->input->post('tax_category_id'); + $item_data['tax_category_id'] = empty($this->input->post('tax_category_id')) ? NULL : $this->input->post('tax_category_id'); } if(!empty($upload_data['orig_name'])) { - // XSS file image sanity check - if($this->xss_clean($upload_data['raw_name'], TRUE) === TRUE) + if($this->xss_clean($upload_data['orig_name'], TRUE) === TRUE) { - $item_data['pic_filename'] = $upload_data['raw_name']; + $item_data['pic_filename'] = $upload_data['orig_name']; } } @@ -557,8 +566,7 @@ class Items extends Secure_Controller $success = TRUE; $new_item = FALSE; - //New item - if($item_id == -1) + if($item_id === NEW_ITEM) { $item_id = $item_data['item_id']; $new_item = TRUE; @@ -568,33 +576,41 @@ class Items extends Secure_Controller if(!$use_destination_based_tax) { - $items_taxes_data = array(); + $items_taxes_data = []; $tax_names = $this->input->post('tax_names'); $tax_percents = $this->input->post('tax_percents'); - $count = count($tax_percents); - for ($k = 0; $k < $count; ++$k) + + $tax_name_index = 0; + + foreach($tax_percents as $tax_percent) { - $tax_percentage = parse_tax($tax_percents[$k]); + $tax_percentage = parse_tax($tax_percent); + if(is_numeric($tax_percentage)) { - $items_taxes_data[] = array('name' => $tax_names[$k], 'percent' => $tax_percentage); + $items_taxes_data[] = array('name' => $tax_names[$tax_name_index], 'percent' => $tax_percentage); } + + $tax_name_index++; } $success &= $this->Item_taxes->save($items_taxes_data, $item_id); } - //Save item quantity + //Save item quantity $stock_locations = $this->Stock_location->get_undeleted_all()->result_array(); foreach($stock_locations as $location) { $updated_quantity = parse_quantity($this->input->post('quantity_' . $location['location_id'])); + if($item_data['item_type'] == ITEM_TEMP) { $updated_quantity = 0; } - $location_detail = array('item_id' => $item_id, - 'location_id' => $location['location_id'], - 'quantity' => $updated_quantity); + + $location_detail = array( + 'item_id' => $item_id, + 'location_id' => $location['location_id'], + 'quantity' => $updated_quantity); $item_quantity = $this->Item_quantity->get_item_quantity($item_id, $location['location_id']); @@ -615,18 +631,21 @@ class Items extends Secure_Controller } } - // Save item attributes - $attribute_links = $this->input->post('attribute_links') != NULL ? $this->input->post('attribute_links') : array(); + // Save item attributes + $attribute_links = $this->input->post('attribute_links') !== NULL ? $this->input->post('attribute_links') : []; $attribute_ids = $this->input->post('attribute_ids'); + $this->Attribute->delete_link($item_id); foreach($attribute_links as $definition_id => $attribute_id) { $definition_type = $this->Attribute->get_info($definition_id)->definition_type; - if($definition_type != DROPDOWN) + + if($definition_type !== DROPDOWN) { $attribute_id = $this->Attribute->save_value($attribute_id, $definition_id, $item_id, $attribute_ids[$definition_id], $definition_type); } + $this->Attribute->save_link($item_id, $definition_id, $attribute_id); } @@ -643,11 +662,11 @@ class Items extends Secure_Controller echo json_encode(array('success' => FALSE, 'message' => $message, 'id' => $item_id)); } } - else //Failure + else { $message = $this->xss_clean($this->lang->line('items_error_adding_updating') . ' ' . $item_data['name']); - echo json_encode(array('success' => FALSE, 'message' => $message, 'id' => -1)); + echo json_encode(array('success' => FALSE, 'message' => $message, 'id' => NEW_ITEM)); } } @@ -662,7 +681,7 @@ class Items extends Secure_Controller */ public function check_kit_exists() { - if($this->input->post('item_number') === -1) + if($this->input->post('item_number') === NEW_ITEM) { $exists = $this->Item_kit->item_kit_exists_for_name($this->input->post('name')); } @@ -676,7 +695,7 @@ class Items extends Secure_Controller /* * Let files be uploaded with their original name */ - private function _handle_image_upload() + private function handle_image_upload() { //Load upload library $config = array('upload_path' => './uploads/item_pics/', @@ -684,10 +703,11 @@ class Items extends Secure_Controller 'max_size' => $this->config->item('image_max_size'), 'max_width' => $this->config->item('image_max_width'), 'max_height' => $this->config->item('image_max_height')); + $this->load->library('upload', $config); $this->upload->do_upload('item_image'); - return strlen($this->upload->display_errors()) == 0 || !strcmp($this->upload->display_errors(), '
'.$this->lang->line('upload_no_file_selected').'
'); + return strlen($this->upload->display_errors()) === 0 || !strcmp($this->upload->display_errors(), '' . $this->lang->line('upload_no_file_selected') . '
'); } public function remove_logo($item_id) @@ -698,7 +718,7 @@ class Items extends Secure_Controller echo json_encode(array('success' => $result)); } - public function save_inventory($item_id = -1) + public function save_inventory($item_id = NEW_ITEM) { $employee_id = $this->Employee->get_logged_in_employee_info()->person_id; $cur_item_info = $this->Item->get_info($item_id); @@ -724,51 +744,50 @@ class Items extends Secure_Controller if($this->Item_quantity->save($item_quantity_data, $item_id, $location_id)) { - $message = $this->xss_clean($this->lang->line('items_successful_updating') . ' ' . $cur_item_info->name); + $message = $this->xss_clean($this->lang->line('items_successful_updating') . " $cur_item_info->name"); echo json_encode(array('success' => TRUE, 'message' => $message, 'id' => $item_id)); } - else //failure + else { - $message = $this->xss_clean($this->lang->line('items_error_adding_updating') . ' ' . $cur_item_info->name); + $message = $this->xss_clean($this->lang->line('items_error_adding_updating') . " $cur_item_info->name"); - echo json_encode(array('success' => FALSE, 'message' => $message, 'id' => -1)); + echo json_encode(array('success' => FALSE, 'message' => $message, 'id' => NEW_ITEM)); } } public function bulk_update() { $items_to_update = $this->input->post('item_ids'); - $item_data = array(); + $item_data = []; foreach($_POST as $key => $value) { - //This field is nullable, so treat it differently - if($key == 'supplier_id' && $value != '') + //This field is nullable, so treat it differently + if($key === 'supplier_id' && $value !== '') { - $item_data["$key"] = $value; + $item_data[$key] = $value; } - elseif($value != '' && !(in_array($key, array('item_ids', 'tax_names', 'tax_percents')))) + elseif($value !== '' && !(in_array($key, array('item_ids', 'tax_names', 'tax_percents')))) { - $item_data["$key"] = $value; + $item_data[$key] = $value; } } //Item data could be empty if tax information is being updated if(empty($item_data) || $this->Item->update_multiple($item_data, $items_to_update)) { - $items_taxes_data = array(); + $items_taxes_data = []; $tax_names = $this->input->post('tax_names'); $tax_percents = $this->input->post('tax_percents'); $tax_updated = FALSE; - $count = count($tax_percents); - for($k = 0; $k < $count; ++$k) + + foreach($tax_percents as $tax_percent) { - if(!empty($tax_names[$k]) && is_numeric($tax_percents[$k])) + if(!empty($tax_names[$tax_percent]) && is_numeric($tax_percents[$tax_percent])) { $tax_updated = TRUE; - - $items_taxes_data[] = array('name' => $tax_names[$k], 'percent' => $tax_percents[$k]); + $items_taxes_data[] = array('name' => $tax_names[$tax_percent], 'percent' => $tax_percents[$tax_percent]); } } @@ -800,15 +819,13 @@ class Items extends Secure_Controller } } - /* - Items import from csv spreadsheet - */ - public function csv() + public function generate_csv_file() { $name = 'import_items.csv'; $allowed_locations = $this->Stock_location->get_allowed_locations(); $allowed_attributes = $this->Attribute->get_definition_names(FALSE); - $data = generate_import_items_csv($allowed_locations,$allowed_attributes); + $data = generate_import_items_csv($allowed_locations, $allowed_attributes); + force_download($name, $data, TRUE); } @@ -820,9 +837,9 @@ class Items extends Secure_Controller /** * Imports items from CSV formatted file. */ - public function do_csv_import() + public function import_csv_file() { - if($_FILES['file_path']['error'] != UPLOAD_ERR_OK) + if($_FILES['file_path']['error'] !== UPLOAD_ERR_OK) { echo json_encode(array('success' => FALSE, 'message' => $this->lang->line('items_csv_import_failed'))); } @@ -830,62 +847,98 @@ class Items extends Secure_Controller { if(file_exists($_FILES['file_path']['tmp_name'])) { - $line_array = get_csv_file($_FILES['file_path']['tmp_name']); - $failCodes = array(); - $keys = $line_array[0]; + set_time_limit(240); - $this->db->trans_begin(); - for($i = 1; $i < count($line_array); $i++) + $failCodes = []; + $third_party_data = []; + $csv_rows = get_csv_file($_FILES['file_path']['tmp_name']); + $employee_id = $this->Employee->get_logged_in_employee_info()->person_id; + $allowed_stock_locations = $this->Stock_location->get_allowed_locations(); + $attribute_definition_names = $this->Attribute->get_definition_names(); + + unset($attribute_definition_names[-1]); //Removes the common_none_selected_text from the array + + foreach($attribute_definition_names as $definition_name) { - $invalidated = FALSE; + $attribute_data[$definition_name] = $this->Attribute->get_definition_by_name($definition_name)[0]; - $line = array_combine($keys,$this->xss_clean($line_array[$i])); //Build a XSS-cleaned associative array with the row to use to assign values - - $item_data = array( - 'name' => $line['Item Name'], - 'description' => $line['Description'], - 'category' => $line['Category'], - 'cost_price' => $line['Cost Price'], - 'unit_price' => $line['Unit Price'], - 'reorder_level' => $line['Reorder Level'], - 'supplier_id' => $this->Supplier->exists($line['Supplier ID']) ? $line['Supplier ID'] : NULL, - 'allow_alt_description' => empty($line['Allow Alt Description'])? '0' : '1', - 'is_serialized' => empty($line['Item has Serial Number'])? '0' : '1', - 'hsn_code' => $line['HSN'], - 'pic_filename' => $line['item_image'] - ); - - $item_number = $line['Barcode']; - - if(!empty($item_number)) + if($attribute_data[$definition_name]['definition_type'] === DROPDOWN) { - $item_data['item_number'] = $item_number; - $invalidated = $this->Item->item_number_exists($item_number); - } - - //Sanity check of data - if(!$invalidated) - { - $invalidated = $this->data_error_check($line, $item_data); - } - - //Save to database - if(!$invalidated && $this->Item->save($item_data)) - { - $this->save_tax_data($line, $item_data); - $this->save_inventory_quantities($line, $item_data); - $this->save_attribute_data($line, $item_data); - } - - //Insert or update item failure - else - { - $failed_row = $i+1; - $failCodes[] = $failed_row; - log_message("ERROR","CSV Item import failed on line ". $failed_row .". This item was not imported."); + $attribute_data[$definition_name]['dropdown_values'] = $this->Attribute->get_definition_values($attribute_data[$definition_name]['definition_id']); } } + $this->db->trans_begin(); + + foreach($csv_rows as $key => $row) + { + $is_failed_row = FALSE; + $item_id = $row['Id']; + $is_update = !empty($item_id); + $item_data = array( + 'item_id' => $item_id, + 'name' => $row['Item Name'], + 'description' => $row['Description'], + 'category' => $row['Category'], + 'cost_price' => $row['Cost Price'], + 'unit_price' => $row['Unit Price'], + 'reorder_level' => $row['Reorder Level'], + 'deleted' => FALSE, + 'hsn_code' => $row['HSN'], + 'pic_filename' => $row['Image']); + + if(!empty($row['supplier_id'])) + { + $item_data['supplier_id'] = $this->Supplier->exists($row['Supplier ID']) ? $row['Supplier ID'] : NULL; + } + + if($is_update) + { + $item_data['allow_alt_description'] = empty($row['Allow Alt Description']) ? NULL : $row['Allow Alt Description']; + $item_data['is_serialized'] = empty($row['Item has Serial Number']) ? NULL : $row['Item has Serial Number']; + } + else + { + $item_data['allow_alt_description'] = empty($row['Allow Alt Description'])? '0' : '1'; + $item_data['is_serialized'] = empty($row['Item has Serial Number'])? '0' : '1'; + } + + if(!empty($row['Barcode'])) + { + $item_data['item_number'] = $row['Barcode']; + $is_failed_row = $this->Item->item_number_exists($item_data['item_number']); + } + + if(!$is_failed_row) + { + $is_failed_row = $this->data_error_check($row, $item_data, $allowed_stock_locations, $attribute_definition_names, $attribute_data); + } + + $item_data = $this->item_lib->custom_array_filter($item_data); + + if(!$is_failed_row && $this->Item->save($item_data, $item_id)) + { + $this->save_tax_data($row, $item_data); + $this->save_inventory_quantities($row, $item_data, $allowed_stock_locations, $employee_id); + $is_failed_row = $this->save_attribute_data($row, $item_data, $attribute_data); + + if($is_update) + { + $item_data = array_merge($item_data, get_object_vars($this->Item->get_info_by_id_or_number($item_id))); + } + } + else + { + $failed_row = $key+2; + $failCodes[] = $failed_row; + log_message('ERROR',"CSV Item import failed on line $failed_row. This item was not imported."); + } + + unset($csv_rows[$key]); + } + + $csv_rows = NULL; + if(count($failCodes) > 0) { $message = $this->lang->line('items_csv_import_partially_failed', count($failCodes), implode(', ', $failCodes)); @@ -895,6 +948,7 @@ class Items extends Secure_Controller else { $this->db->trans_commit(); + echo json_encode(array('success' => TRUE, 'message' => $this->lang->line('items_csv_import_success'))); } } @@ -913,92 +967,97 @@ class Items extends Secure_Controller * * @return bool Returns FALSE if all data checks out and TRUE when there is an error in the data */ - private function data_error_check($line, $item_data) + private function data_error_check($row, $item_data, $allowed_locations, $definition_names, $attribute_data) { - //Check for empty required fields + $item_id = $row['Id']; + $is_update = $item_id ? TRUE : FALSE; + + //Check for empty required fields $check_for_empty = array( - $item_data['name'], - $item_data['category'], - $item_data['unit_price'] - ); + 'name' => $item_data['name'], + 'category' => $item_data['category'], + 'unit_price' => $item_data['unit_price']); foreach($check_for_empty as $key => $val) { - if (empty($val)) + if (empty($val) && !$is_update) { - log_message("ERROR","Empty required value"); - return TRUE; //Return fail on empty required fields - } - } - - $item_data['cost_price'] = empty($item_data['cost_price']) ? 0 : 1; //Allow for zero wholesale price - - //Build array of fields to check for numerics - $check_for_numeric_values = array( - $item_data['cost_price'], - $item_data['unit_price'], - $item_data['reorder_level'], - $item_data['supplier_id'], - $line['Tax 1 Percent'], - $line['Tax 2 Percent'] - ); - - //Add in Stock Location values to check for numeric - $allowed_locations = $this->Stock_location->get_allowed_locations(); - - foreach($allowed_locations as $location_id => $location_name) - { - $check_for_numeric_values[] = $line['location_'. $location_name]; - } - - //Check for non-numeric values which require numeric - foreach($check_for_numeric_values as $value) - { - if(!is_numeric($value) && $value != '') - { - log_message("ERROR","non-numeric: '$value' when numeric is required"); + log_message('Error',"Empty required value in $key."); return TRUE; } } - //Check Attribute Data - $definition_names = $this->Attribute->get_definition_names(); - unset($definition_names[-1]); + if(!$is_update) + { + $item_data['cost_price'] = empty($item_data['cost_price']) ? 0 : $item_data['cost_price']; //Allow for zero wholesale price + } + else + { + if(!$this->Item->exists($item_id)) + { + log_message('Error',"non-existent item_id: '$item_id' when either existing item_id or no item_id is required."); + return TRUE; + } + } + //Build array of fields to check for numerics + $check_for_numeric_values = array( + 'cost_price' => $item_data['cost_price'], + 'unit_price' => $item_data['unit_price'], + 'reorder_level' => $item_data['reorder_level'], + 'supplier_id' => $item_data['supplier_id'], + 'Tax 1 Percent' => $row['Tax 1 Percent'], + 'Tax 2 Percent' => $row['Tax 2 Percent']); + + foreach($allowed_locations as $location_name) + { + $check_for_numeric_values[] = $row["location_$location_name"]; + } + + //Check for non-numeric values which require numeric + foreach($check_for_numeric_values as $key => $value) + { + if(!is_numeric($value) && !empty($value)) + { + log_message('Error',"non-numeric: '$value' for '$key' when numeric is required"); + return TRUE; + } + } + + //Check Attribute Data foreach($definition_names as $definition_name) { - if(!empty($line['attribute_' . $definition_name])) + if(!empty($row["attribute_$definition_name"])) { - $attribute_data = $this->Attribute->get_definition_by_name($definition_name)[0]; - $attribute_type = $attribute_data['definition_type']; - $attribute_value = $line['attribute_' . $definition_name]; + $definition_type = $attribute_data[$definition_name]['definition_type']; + $attribute_value = $row["attribute_$definition_name"]; - if($attribute_type == 'DROPDOWN') + switch($definition_type) { - $dropdown_values = $this->Attribute->get_definition_values($attribute_data['definition_id']); - $dropdown_values[] = ''; + case DROPDOWN: + $dropdown_values = $attribute_data[$definition_name]['dropdown_values']; + $dropdown_values[] = ''; - if(in_array($attribute_value, $dropdown_values) === FALSE && !empty($attribute_value)) - { - log_message("ERROR","Value: '$attribute_value' is not an acceptable DROPDOWN value"); - return TRUE; - } - } - else if($attribute_type == 'DECIMAL') - { - if(!is_numeric($attribute_value) && !empty($attribute_value)) - { - log_message("ERROR","'$attribute_value' is not an acceptable DECIMAL value"); - return TRUE; - } - } - else if($attribute_type == 'DATETIME') - { - if(strtotime($attribute_value) === FALSE && !empty($attribute_value)) - { - log_message("ERROR","'$attribute_value' is not an acceptable DATETIME value."); - return TRUE; - } + if(!empty($attribute_value) && in_array($attribute_value, $dropdown_values) === FALSE) + { + log_message('Error',"Value: '$attribute_value' is not an acceptable DROPDOWN value"); + return TRUE; + } + break; + case DECIMAL: + if(!is_numeric($attribute_value) && !empty($attribute_value)) + { + log_message('Error',"'$attribute_value' is not an acceptable DECIMAL value"); + return TRUE; + } + break; + case DATE: + if(valid_date($attribute_value) === FALSE && !empty($attribute_value)) + { + log_message('Error',"'$attribute_value' is not an acceptable DATE value. The value must match the set locale."); + return TRUE; + } + break; } } } @@ -1013,88 +1072,100 @@ class Items extends Secure_Controller * @param failCodes * @param attribute_data */ - private function save_attribute_data($line, $item_data) + private function save_attribute_data($row, $item_data, $definitions) { - $definition_names = $this->Attribute->get_definition_names(); - unset($definition_names[-1]); - - foreach($definition_names as $definition_name) + foreach($definitions as $definition) { - //Create attribute value - if(!empty($line['attribute_' . $definition_name]) || $line['attribute_' . $definition_name] == '0') + $attribute_name = $definition['definition_name']; + $attribute_value = $row["attribute_$attribute_name"]; + + //Create attribute value + if(!empty($attribute_value) || $attribute_value === '0') { - $attribute_data = $this->Attribute->get_definition_by_name($definition_name)[0]; - - //CHECKBOX Attribute types (zero value creates attribute and marks it as unchecked) - if($attribute_data['definition_type'] == 'CHECKBOX') + if($definition['definition_type'] === CHECKBOX) { - //FALSE and '0' value creates checkbox and marks it as unchecked. - if(strcasecmp($line['attribute_' . $definition_name],'FALSE') == 0 || $line['attribute_' . $definition_name] == '0') - { - $line['attribute_' . $definition_name] = '0'; - } - else - { - $line['attribute_' . $definition_name] = '1'; - } + $checkbox_is_unchecked = (strcasecmp($attribute_value,'FALSE') === 0 || $attribute_value === '0'); + $attribute_value = $checkbox_is_unchecked ? '0' : '1'; - $status = $this->Attribute->save_value($line['attribute_' . $definition_name], $attribute_data['definition_id'], $item_data['item_id'], FALSE, $attribute_data['definition_type']); + $attribute_id = $this->store_attribute_value($attribute_value, $definition, $item_data['item_id']); + } + elseif(!empty($attribute_value)) + { + $attribute_id = $this->store_attribute_value($attribute_value, $definition, $item_data['item_id']); + } + else + { + return TRUE; } - //All other Attribute types (0 value means attribute not created) - elseif(!empty($line['attribute_' . $definition_name])) + if($attribute_id === FALSE) { - $status = $this->Attribute->save_value($line['attribute_' . $definition_name], $attribute_data['definition_id'], $item_data['item_id'], FALSE, $attribute_data['definition_type']); - } - - if($status === FALSE) - { - return FALSE; + return TRUE; } } } } + /** + * Saves the attribute_value and attribute_link if necessary + */ + private function store_attribute_value($value, $attribute_data, $item_id) + { + $attribute_id = $this->Attribute->value_exists($value, $attribute_data['definition_type']); + + $this->Attribute->delete_link($item_id, $attribute_data['definition_id']); + + if($attribute_id === FALSE) + { + $attribute_id = $this->Attribute->save_value($value, $attribute_data['definition_id'], $item_id, FALSE, $attribute_data['definition_type']); + } + else if($this->Attribute->save_link($item_id, $attribute_data['definition_id'], $attribute_id) === FALSE) + { + return FALSE; + } + return $attribute_id; + } + /** * Saves inventory quantities for the row in the appropriate stock locations. * * @param array line * @param item_data */ - private function save_inventory_quantities($line, $item_data) + private function save_inventory_quantities($row, $item_data, $allowed_locations, $employee_id) { - //Quantities & Inventory Section - $employee_id = $this->Employee->get_logged_in_employee_info()->person_id; - $emp_info = $this->Employee->get_info($employee_id); + //Quantities & Inventory Section $comment = $this->lang->line('items_inventory_CSV_import_quantity'); - $allowed_locations = $this->Stock_location->get_allowed_locations(); + $is_update = $row['Id'] ? TRUE : FALSE; foreach($allowed_locations as $location_id => $location_name) { $item_quantity_data = array( - 'item_id' => $item_data['item_id'], - 'location_id' => $location_id - ); + 'item_id' => $item_data['item_id'], + 'location_id' => $location_id); $csv_data = array( - 'trans_items' => $item_data['item_id'], - 'trans_user' => $employee_id, - 'trans_comment' => $comment, - 'trans_location' => $location_id, - ); + 'trans_items' => $item_data['item_id'], + 'trans_user' => $employee_id, + 'trans_comment' => $comment, + 'trans_location' => $location_id); - if(!empty($line['location_' . $location_name])) + if(!empty($row["location_$location_name"]) || $row["location_$location_name"] === '0') { - $item_quantity_data['quantity'] = $line['location_' . $location_name]; + $item_quantity_data['quantity'] = $row["location_$location_name"]; $this->Item_quantity->save($item_quantity_data, $item_data['item_id'], $location_id); - $csv_data['trans_inventory'] = $line['location_' . $location_name]; + $csv_data['trans_inventory'] = $row["location_$location_name"]; $this->Inventory->insert($csv_data); } + elseif($is_update) + { + return; + } else { $item_quantity_data['quantity'] = 0; - $this->Item_quantity->save($item_quantity_data, $item_data['item_id'], $line[$col]); + $this->Item_quantity->save($item_quantity_data, $item_data['item_id'], $location_id); $csv_data['trans_inventory'] = 0; $this->Inventory->insert($csv_data); @@ -1107,21 +1178,21 @@ class Items extends Secure_Controller * * @param array line */ - private function save_tax_data($line, $item_data) + private function save_tax_data($row, $item_data) { - $items_taxes_data = array(); + $items_taxes_data = []; - if(is_numeric($line['Tax 1 Percent']) && $line['Tax 1 Name'] != '') + if(is_numeric($row['Tax 1 Percent']) && $row['Tax 1 Name'] !== '') { - $items_taxes_data[] = array('name' => $line['Tax 1 Name'], 'percent' => $line['Tax 1 Percent'] ); + $items_taxes_data[] = array('name' => $row['Tax 1 Name'], 'percent' => $row['Tax 1 Percent']); } - if(is_numeric($line['Tax 2 Percent']) && $line['Tax 2 Name'] != '') + if(is_numeric($row['Tax 2 Percent']) && $row['Tax 2 Name'] !== '') { - $items_taxes_data[] = array('name' => $line['Tax 2 Name'], 'percent' => $line['Tax 2 Percent'] ); + $items_taxes_data[] = array('name' => $row['Tax 2 Name'], 'percent' => $row['Tax 2 Percent']); } - if(count($items_taxes_data) > 0) + if(isset($items_taxes_data)) { $this->Item_taxes->save($items_taxes_data, $item_data['item_id']); } @@ -1130,19 +1201,19 @@ class Items extends Secure_Controller /** * Guess whether file extension is not in the table field, if it isn't, then it's an old-format (formerly pic_id) field, so we guess the right filename and update the table * - * @param $item the item to update + * @param $item int item to update */ - private function _update_pic_filename($item) + private function update_pic_filename($item) { $filename = pathinfo($item->pic_filename, PATHINFO_FILENAME); - // if the field is empty there's nothing to check + // if the field is empty there's nothing to check if(!empty($filename)) { $ext = pathinfo($item->pic_filename, PATHINFO_EXTENSION); if(empty($ext)) { - $images = glob('./uploads/item_pics/' . $item->pic_filename . '.*'); + $images = glob("./uploads/item_pics/$item->pic_filename.*"); if(sizeof($images) > 0) { $new_pic_filename = pathinfo($images[0], PATHINFO_BASENAME); diff --git a/application/helpers/importfile_helper.php b/application/helpers/importfile_helper.php index 97da2baba..e5e6036f6 100644 --- a/application/helpers/importfile_helper.php +++ b/application/helpers/importfile_helper.php @@ -1,30 +1,20 @@ $location_name) + foreach($locations as $location_name) { $location_headers .= ',"location_' . $location_name . '"'; } @@ -32,14 +22,9 @@ function generate_stock_location_headers($locations) return $location_headers; } -/** - * Generates a list of attribute names as a string - * - * @return string Comma-separated list of attribute names - */ function generate_attribute_headers($attribute_names) { - $attribute_headers = ""; + $attribute_headers = ''; unset($attribute_names[-1]); foreach($attribute_names as $attribute_name) @@ -50,61 +35,59 @@ function generate_attribute_headers($attribute_names) return $attribute_headers; } -/** - * Read the contents of a given CSV formatted file into a two-dimensional array - * - * @param string $file_name Name of the file to read. - * @return boolean|array[][] two-dimensional array with the file contents or FALSE on failure. - */ function get_csv_file($file_name) { - ini_set("auto_detect_line_endings", true); +//TODO: current implementation reads the entire file in. This is memory intensive for large files. +//We may want to rework the CSV import feature to read the file in chunks, process it and continue. +//It must be done in a way that does not significantly negatively affect performance. + ini_set('auto_detect_line_endings', true); + + $csv_rows = FALSE; if(($csv_file = fopen($file_name,'r')) !== FALSE) { + $CI =& get_instance(); + $CI->load->helper('security'); + + $csv_rows = []; + //Skip Byte-Order Mark if(bom_exists($csv_file) === TRUE) { fseek($csv_file, 3); } - while (($data = fgetcsv($csv_file)) !== FALSE) + $headers = fgetcsv($csv_file); + + while(($row = fgetcsv($csv_file)) !== FALSE) { - //Skip empty lines - if(array(null) !== $data) + //Skip empty lines + if($row !== array(null)) { - $line_array[] = $data; + $csv_rows[] = array_combine($headers, $CI->security->xss_clean($row)); } } - } - else - { - return FALSE; + + fclose($csv_file); } - return $line_array; + return $csv_rows; } -/** - * Checks the first three characters of a file for the Byte-Order Mark then returns the file position to the first character. - * - * @param object $file_handle File handle to check - * @return bool Returns TRUE if the BOM exists and FALSE otherwise. - */ function bom_exists(&$file_handle) { - $str = fread($file_handle,3); + $result = FALSE; + $candidate = fread($file_handle, 3); + rewind($file_handle); - $bom = pack("CCC", 0xef, 0xbb, 0xbf); + $bom = pack('CCC', 0xef, 0xbb, 0xbf); - if (0 === strncmp($str, $bom, 3)) + if (0 === strncmp($candidate, $bom, 3)) { - return TRUE; - } - else - { - return FALSE; + $result = TRUE; } + + return $result; } ?> \ No newline at end of file diff --git a/application/helpers/locale_helper.php b/application/helpers/locale_helper.php index 279d2045d..25a7f5d9e 100644 --- a/application/helpers/locale_helper.php +++ b/application/helpers/locale_helper.php @@ -249,7 +249,7 @@ function get_payment_options() $config = get_instance()->config; $lang = get_instance()->lang; - $payments = array(); + $payments = []; if($config->item('payment_options_order') == 'debitcreditcash') @@ -388,7 +388,7 @@ function to_quantity_decimals($number) return to_decimals($number, 'quantity_decimals'); } -function to_decimals($number, $decimals=NULL, $type=\NumberFormatter::DECIMAL) +function to_decimals($number, $decimals = NULL, $type=\NumberFormatter::DECIMAL) { // ignore empty strings and return // NOTE: do not change it to empty otherwise tables will show a 0 with no decimal nor currency symbol @@ -424,7 +424,6 @@ function parse_tax($number) function parse_decimals($number, $decimals = NULL) { // ignore empty strings and return - if(empty($number)) { return $number; @@ -442,7 +441,7 @@ function parse_decimals($number, $decimals = NULL) $config = get_instance()->config; - if($decimals == NULL) + if($decimals === NULL) { $decimals = $config->item('currency_decimals'); } @@ -600,12 +599,13 @@ function dateformat_bootstrap($php_format) function valid_date($date) { - return preg_match('/^([0-9]{2,4})-([0-1][0-9])-([0-3][0-9])(?:( [0-2][0-9]):([0-5][0-9]):([0-5][0-9]))?$/', $date); + $config = get_instance()->Appconfig; + return (DateTime::createFromFormat($config->get('dateformat'), $date)); } function valid_decimal($decimal) { - return preg_match('/^(\d*\.)?\d+$/', $decimal); + return (preg_match('/^(\d*\.)?\d+$/', $decimal) === 1); } ?> diff --git a/application/models/Attribute.php b/application/models/Attribute.php index 62dc0e987..504251504 100644 --- a/application/models/Attribute.php +++ b/application/models/Attribute.php @@ -21,18 +21,24 @@ class Attribute extends CI_Model */ public function exists($definition_id, $deleted = FALSE) { - $this->db->from('attribute_definitions'); $this->db->where('definition_id', $definition_id); $this->db->where('deleted', $deleted); - return ($this->db->get()->num_rows() == 1); + return ($this->db->get('attribute_definitions')->num_rows() == 1); } + /** + * Returns whether an attribute_link row exists given an item_id and optionally a definition_id + * @param int $item_id + * @param boolean $definition_id + * @return boolean TRUE if at least one attribute_link exists or FALSE if no attributes exist. + */ public function link_exists($item_id, $definition_id = FALSE) { + $this->db->where('item_id', $item_id); $this->db->where('sale_id'); $this->db->where('receiving_id'); - $this->db->from('attribute_links'); + if(empty($definition_id)) { $this->db->where('definition_id <>'); @@ -43,25 +49,38 @@ class Attribute extends CI_Model $this->db->where('definition_id', $definition_id); } - $this->db->where('item_id', $item_id); - - return ($this->db->get()->num_rows() > 0); + return ($this->db->get('attribute_links')->num_rows() > 0); } /* - Determines if a given attribute_value exists in the attribute_values table and returns the attribute_id if it does + * Determines if a given attribute_value exists in the attribute_values table and returns the attribute_id if it does */ - public function value_exists($attribute_value) + public function value_exists($attribute_value, $definition_type = TEXT) { - $this->db->distinct('attribute_id'); - $this->db->from('attribute_values'); - $this->db->where('attribute_value', $attribute_value); + switch($definition_type) + { + case DATE: + $data_type = 'date'; + $attribute_date_value = DateTime::createFromFormat($this->Appconfig->get('dateformat'), $attribute_value); + $attribute_value = $attribute_date_value->format('Y-m-d'); + break; + case DECIMAL: + $data_type = 'decimal'; + break; + default: + $data_type = 'value'; + break; + } - $query = $this->db->get(); - if ($query->num_rows() > 0) + $this->db->select('attribute_id'); + $this->db->where("attribute_$data_type", $attribute_value); + $query = $this->db->get('attribute_values'); + + if($query->num_rows() > 0) { return $query->row()->attribute_id; } + return FALSE; } @@ -86,7 +105,7 @@ class Attribute extends CI_Model //Get empty base parent object, as $item_id is NOT an item $item_obj = new stdClass(); - //Get all the fields from items table + //Get all the fields from attribute_definitions table foreach($this->db->list_fields('attribute_definitions') as $field) { $item_obj->$field = ''; @@ -106,9 +125,10 @@ class Attribute extends CI_Model $this->db->join('attribute_definitions AS parent_definition', 'parent_definition.definition_id = definition.definition_fk', 'left'); $this->db->group_start(); - $this->db->like('definition.definition_name', $search); - $this->db->or_like('definition.definition_type', $search); + $this->db->like('definition.definition_name', $search); + $this->db->or_like('definition.definition_type', $search); $this->db->group_end(); + $this->db->where('definition.deleted', 0); $this->db->order_by($sort, $order); @@ -122,68 +142,63 @@ class Attribute extends CI_Model public function get_attributes_by_item($item_id) { - $this->db->from('attribute_definitions'); $this->db->join('attribute_links', 'attribute_links.definition_id = attribute_definitions.definition_id'); $this->db->where('item_id', $item_id); - $this->db->where('receiving_id'); $this->db->where('sale_id'); + $this->db->where('receiving_id'); $this->db->where('deleted', 0); - $this->db->order_by('definition_name','ASC'); + $this->db->order_by('definition_name', 'ASC'); - $results = $this->db->get()->result_array(); + $results = $this->db->get('attribute_definitions')->result_array(); - return $this->_to_array($results, 'definition_id'); + return $this->to_array($results, 'definition_id'); } public function get_values_by_definitions($definition_ids) { if(count($definition_ids ? : [])) { - $this->db->from('attribute_definitions'); - $this->db->group_start(); - $this->db->where_in('definition_fk', array_keys($definition_ids)); - $this->db->or_where_in('definition_id', array_keys($definition_ids)); - $this->db->where('definition_type !=', GROUP); + $this->db->where_in('definition_fk', array_keys($definition_ids)); + $this->db->or_where_in('definition_id', array_keys($definition_ids)); + $this->db->where('definition_type !=', GROUP); $this->db->group_end(); $this->db->where('deleted', 0); - $results = $this->db->get()->result_array(); + $results = $this->db->get('attribute_definitions')->result_array(); - return $this->_to_array($results, 'definition_id'); + return $this->to_array($results, 'definition_id'); } - return array(); + return []; } public function get_definitions_by_type($attribute_type, $definition_id = NO_DEFINITION_ID) { - $this->db->from('attribute_definitions'); $this->db->where('definition_type', $attribute_type); $this->db->where('deleted', 0); + $this->db->where('definition_fk'); if($definition_id != CATEGORY_DEFINITION_ID) { - $this->db->where('definition_id != ', $definition_id); + $this->db->where('definition_id <>', $definition_id); } - $this->db->where('definition_fk'); - $results = $this->db->get()->result_array(); + $results = $this->db->get('attribute_definitions')->result_array(); - return $this->_to_array($results, 'definition_id', 'definition_name'); + return $this->to_array($results, 'definition_id', 'definition_name'); } public function get_definitions_by_flags($definition_flags) { - $this->db->from('attribute_definitions'); $this->db->where('definition_flags &', $definition_flags); $this->db->where('deleted', 0); $this->db->where('definition_type <>', GROUP); $this->db->order_by('definition_id'); - $results = $this->db->get()->result_array(); + $results = $this->db->get('attribute_definitions')->result_array(); - return $this->_to_array($results, 'definition_id', 'definition_name'); + return $this->to_array($results, 'definition_id', 'definition_name'); } /** @@ -194,7 +209,6 @@ class Attribute extends CI_Model */ public function get_definition_names($groups = TRUE) { - $this->db->from('attribute_definitions'); $this->db->where('deleted', 0); $this->db->order_by('definition_name','ASC'); @@ -203,11 +217,10 @@ class Attribute extends CI_Model $this->db->where_not_in('definition_type',GROUP); } - $results = $this->db->get()->result_array(); - + $results = $this->db->get('attribute_definitions')->result_array(); $definition_name = array(-1 => $this->lang->line('common_none_selected_text')); - return $definition_name + $this->_to_array($results, 'definition_id', 'definition_name'); + return $definition_name + $this->to_array($results, 'definition_id', 'definition_name'); } public function get_definition_values($definition_id) @@ -216,23 +229,22 @@ class Attribute extends CI_Model if($definition_id > 0 || $definition_id == CATEGORY_DEFINITION_ID) { - $this->db->from('attribute_links'); $this->db->join('attribute_values', 'attribute_values.attribute_id = attribute_links.attribute_id'); - $this->db->where('definition_id', $definition_id); $this->db->where('item_id'); + $this->db->where('definition_id', $definition_id); $this->db->order_by('attribute_value','ASC'); - $results = $this->db->get()->result_array(); + $results = $this->db->get('attribute_links')->result_array(); - return $this->_to_array($results, 'attribute_id', 'attribute_value'); + return $this->to_array($results, 'attribute_id', 'attribute_value'); } return $attribute_values; } - private function _to_array($results, $key, $value = '') + private function to_array($results, $key, $value = '') { - return array_column(array_map(function($result) use ($key, $value) { + return array_column(array_map(function($result) use ($key, $value){ return [$result[$key], empty($value) ? $result : $result[$value]]; }, $results), 1, 0); } @@ -242,10 +254,9 @@ class Attribute extends CI_Model */ public function get_total_rows() { - $this->db->from('attribute_definitions'); $this->db->where('deleted', 0); - return $this->db->count_all_results(); + return $this->db->count_all_results('attribute_definitions'); } /* @@ -256,73 +267,83 @@ class Attribute extends CI_Model return $this->search($search)->num_rows(); } - private function check_data_validity($definition, $from, $to) + private function check_data_validity($definition_id, $from, $to) { $success = FALSE; if($from === TEXT) { - $this->db->select('item_id,attribute_value'); - $this->db->from('attribute_values'); - $this->db->join('attribute_links', 'attribute_values.attribute_id = attribute_links.attribute_id'); - $this->db->where('definition_id',$definition); $success = TRUE; - if($to === DATE) + $this->db->distinct()->select('attribute_value'); + $this->db->join('attribute_links', 'attribute_values.attribute_id = attribute_links.attribute_id'); + $this->db->where('definition_id', $definition_id); + + foreach($this->db->get('attribute_values')->result() as $attribute) { - foreach($this->db->get()->result_array() as $row) + switch($to) { - if(valid_date($row['attribute_value']) === FALSE) - { - log_message('ERROR', 'item_id: ' . $row['item_id'] . ' with attribute_value: ' . $row['attribute_value'] . ' cannot be converted to datetime'); - $success = FALSE; - } + case DATE: + $success = valid_date($attribute->attribute_value); + break; + case DECIMAL: + $success = valid_decimal($attribute->attribute_value); + break; } - } - else if($to === DECIMAL) - { - foreach($this->db->get()->result_array() as $row) + + if($success === FALSE) { - if(valid_decimal($row['attribute_value']) === FALSE) + $affected_items = $this->get_items_by_value($attribute->attribute_value, $definition_id); + foreach($affected_items as $affected_item) { - log_message('ERROR', 'item_id: ' . $row['item_id'] . ' with attribute_value: ' . $row['attribute_value'] . ' cannot be converted to decimal'); - $success = FALSE; + $affected_items[] = $affected_item['item_id']; } + + log_message('ERROR', "Attribute_value: '$attribute->attribute_value' cannot be converted to $to. Affected Items: ". implode(',', $affected_items)); + unset($affected_items); } } } return $success; } - private function convert_definition_type($definition_id, $from_type, $to_type) + /** + * Returns all item_ids with a specific attribute_value and attribute_definition + * @param string $attribute_value + * @param int $definition_id + * @return array + */ + private function get_items_by_value($attribute_value, $definition_id) + { + $this->db->select('item_id'); + $this->db->join('attribute_values', 'attribute_values.attribute_id = attribute_links.attribute_id'); + $this->db->where('definition_id', $definition_id); + $this->db->where('attribute_value', $attribute_value); + return $this->db->get('attribute_links')->result_array(); + } + + + /** + * Converts data in attribute_values and attribute_links tables associated with the conversion of one attribute type to another. + * @param int $definition_id + * @param string $from_type + * @param string $to_type + * @return boolean + */ + private function convert_definition_data($definition_id, $from_type, $to_type) { $success = FALSE; - //From TEXT if($from_type === TEXT) { - //To DATETIME or DECIMAL if(in_array($to_type, [DATE, DECIMAL], TRUE)) { - $field = ($to_type === DATE ? 'attribute_date' : 'attribute_decimal'); - if($this->check_data_validity($definition_id, $from_type, $to_type)) { - $this->db->trans_start(); - - $query = 'UPDATE ospos_attribute_values '; - $query .= 'INNER JOIN ospos_attribute_links '; - $query .= 'ON ospos_attribute_values.attribute_id = ospos_attribute_links.attribute_id '; - $query .= 'SET '. $field .'= attribute_value, '; - $query .= 'attribute_value = NULL '; - $query .= 'WHERE definition_id = ' . $this->db->escape($definition_id); - $success = $this->db->query($query); - - $this->db->trans_complete(); + $attributes_to_convert = $this->get_attributes_by_definition($definition_id); + $success = $this->attribute_cleanup($attributes_to_convert, $definition_id, $to_type); } } - - //To DROPDOWN or CHECKBOX else if($to_type === DROPDOWN) { $success = TRUE; @@ -333,57 +354,44 @@ class Attribute extends CI_Model $this->db->trans_start(); - $query = 'UPDATE ospos_attribute_values values '; - $query .= 'INNER JOIN ospos_attribute_links links '; - $query .= 'ON values.attribute_id = links.attribute_id '; - $query .= "SET links.attribute_id = IF((values.attribute_value IN('FALSE','0','') OR (values.attribute_value IS NULL)), $checkbox_attribute_values[0], $checkbox_attribute_values[1]) "; - $query .= 'WHERE definition_id = ' . $this->db->escape($definition_id); + $query = 'UPDATE '. $this->db->dbprefix('attribute_links') .' links '; + $query .= 'JOIN '. $this->db->dbprefix('attribute_values') .' vals '; + $query .= 'ON vals.attribute_id = links.attribute_id '; + $query .= "SET links.attribute_id = IF((attribute_value IN('FALSE','0','') OR (attribute_value IS NULL)), $checkbox_attribute_values[0], $checkbox_attribute_values[1]) "; + $query .= 'WHERE definition_id = '. $this->db->escape($definition_id); $success = $this->db->query($query); $this->db->trans_complete(); } } - - //From DROPDOWN else if($from_type === DROPDOWN) { - //To TEXT if(in_array($to_type, [TEXT, CHECKBOX], TRUE)) { - $this->db->trans_start(); - - $this->db->from('ospos_attribute_links'); - $this->db->where('definition_id',$definition_id); - $this->db->where('item_id', NULL); - $success = $this->db->delete(); - - $this->db->trans_complete(); - - //To CHECKBOX if($to_type === CHECKBOX) { $checkbox_attribute_values = $this->checkbox_attribute_values($definition_id); $this->db->trans_start(); - $query = 'UPDATE ospos_attribute_values vals '; - $query .= 'INNER JOIN ospos_attribute_links links '; + $query = 'UPDATE '. $this->db->dbprefix('attribute_links') .' links '; + $query .= 'JOIN '. $this->db->dbprefix('attribute_values') .' vals '; $query .= 'ON vals.attribute_id = links.attribute_id '; - $query .= "SET links.attribute_id = IF((vals.attribute_value IN('FALSE','0','') OR (vals.attribute_value IS NULL)), $checkbox_attribute_values[0], $checkbox_attribute_values[1]) "; - $query .= 'WHERE links.definition_id = ' . $this->db->escape($definition_id); + $query .= "SET links.attribute_id = IF((attribute_value IN('FALSE','0','') OR (attribute_value IS NULL)), $checkbox_attribute_values[0], $checkbox_attribute_values[1]) "; + $query .= 'WHERE definition_id = '. $this->db->escape($definition_id); $success = $this->db->query($query); $this->db->trans_complete(); } } } - - //From any other type else { $success = TRUE; } + $this->delete_orphaned_links($definition_id); + $this->delete_orphaned_values(); return $success; } @@ -410,7 +418,6 @@ class Attribute extends CI_Model */ public function save_definition(&$definition_data, $definition_id = NO_DEFINITION_ID) { - //Run these queries as a transaction, we want to make sure we do all or nothing $this->db->trans_start(); //Definition doesn't exist @@ -418,7 +425,7 @@ class Attribute extends CI_Model { if($this->exists($definition_id,TRUE)) { - $success = $this->undelete($definition_id); + $success = $this->undelete_definition($definition_id); } else { @@ -430,26 +437,27 @@ class Attribute extends CI_Model //Definition already exists else { - $this->db->select('definition_type, definition_name'); - $this->db->from('attribute_definitions'); + //Get current definition type and name + $this->db->select('definition_type'); $this->db->where('definition_id', $definition_id); - $row = $this->db->get()->row(); + $row = $this->db->get('attribute_definitions')->row(); $from_definition_type = $row->definition_type; - $from_definition_name = $row->definition_name; $to_definition_type = $definition_data['definition_type']; + //Update the definition values + $this->db->where('definition_id', $definition_id); + + $success = $this->db->update('attribute_definitions', $definition_data); + $definition_data['definition_id'] = $definition_id; + if($from_definition_type !== $to_definition_type) { - if(!$this->convert_definition_type($definition_id,$from_definition_type,$to_definition_type)) + if($this->convert_definition_data($definition_id, $from_definition_type, $to_definition_type) === FALSE) { return FALSE; } } - - $this->db->where('definition_id', $definition_id); - $success = $this->db->update('attribute_definitions', $definition_data); - $definition_data['definition_id'] = $definition_id; } $this->db->trans_complete(); @@ -461,14 +469,14 @@ class Attribute extends CI_Model public function get_definition_by_name($definition_name, $definition_type = FALSE) { - $this->db->from('attribute_definitions'); $this->db->where('definition_name', $definition_name); + if($definition_type != FALSE) { $this->db->where('definition_type', $definition_type); } - return $this->db->get()->result_array(); + return $this->db->get('attribute_definitions')->result_array(); } public function save_link($item_id, $definition_id, $attribute_id) @@ -485,7 +493,10 @@ class Attribute extends CI_Model } else { - $this->db->insert('attribute_links', array('attribute_id' => $attribute_id, 'item_id' => $item_id, 'definition_id' => $definition_id)); + $this->db->insert('attribute_links', array( + 'attribute_id' => $attribute_id, + 'item_id' => $item_id, + 'definition_id' => $definition_id)); } $this->db->trans_complete(); @@ -493,20 +504,30 @@ class Attribute extends CI_Model return $this->db->trans_status(); } - public function delete_link($item_id) + public function delete_link($item_id, $definition_id = FALSE) { + $delete_data = array('item_id' => $item_id); + + //Exclude rows where sale_id or receiving_id has a value $this->db->where('sale_id'); $this->db->where('receiving_id'); - return $this->db->delete('attribute_links', array('item_id' => $item_id)); + if(!empty($definition_id)) + { + $delete_data += ['definition_id' => $definition_id]; + } + + $success = $this->db->delete('attribute_links', $delete_data); + + return $success; } public function get_link_value($item_id, $definition_id) { $this->db->where('item_id', $item_id); - $this->db->where('definition_id', $definition_id); $this->db->where('sale_id'); $this->db->where('receiving_id'); + $this->db->where('definition_id', $definition_id); return $this->db->get('attribute_links')->row_object(); } @@ -516,11 +537,11 @@ class Attribute extends CI_Model $format = $this->db->escape(dateformat_mysql()); $this->db->select("GROUP_CONCAT(attribute_value SEPARATOR ', ') AS attribute_values"); $this->db->select("GROUP_CONCAT(DATE_FORMAT(attribute_date, $format) SEPARATOR ', ') AS attribute_dtvalues"); - $this->db->from('attribute_links'); $this->db->join('attribute_values', 'attribute_values.attribute_id = attribute_links.attribute_id'); $this->db->join('attribute_definitions', 'attribute_definitions.definition_id = attribute_links.definition_id'); $this->db->where('definition_type <>', GROUP); $this->db->where('deleted', 0); + $this->db->where('item_id', intval($item_id)); if(!empty($id)) { @@ -532,37 +553,47 @@ class Attribute extends CI_Model $this->db->where('receiving_id'); } - $this->db->where('item_id', (int) $item_id); $this->db->where('definition_flags & ', $definition_flags); - return $this->db->get(); + return $this->db->get('attribute_links'); } public function get_attribute_value($item_id, $definition_id) { - $this->db->from('attribute_values'); $this->db->join('attribute_links', 'attribute_links.attribute_id = attribute_values.attribute_id'); - $this->db->where('definition_id', $definition_id); + $this->db->where('item_id', intval($item_id)); $this->db->where('sale_id'); $this->db->where('receiving_id'); - $this->db->where('item_id', (int) $item_id); + $this->db->where('definition_id', $definition_id); - return $this->db->get()->row_object(); + return $this->db->get('attribute_values')->row_object(); } + public function get_attribute_values($item_id) + { + $this->db->select('attribute_values.attribute_value, attribute_values.attribute_decimal, attribute_values.attribute_date, attribute_links.definition_id'); + $this->db->join('attribute_values', 'attribute_links.attribute_id = attribute_values.attribute_id'); + $this->db->where('item_id', intval($item_id)); + + $results = $this->db->get('attribute_links')->result_array(); + + return $this->to_array($results, 'definition_id'); + } + + public function copy_attribute_links($item_id, $sale_receiving_fk, $id) { $this->db->query( - 'INSERT INTO ospos_attribute_links (item_id, definition_id, attribute_id, ' . $sale_receiving_fk . ') - SELECT ' . $this->db->escape($item_id) . ', definition_id, attribute_id, ' . $this->db->escape($id) . ' - FROM ' . $this->db->dbprefix('attribute_links') . ' - WHERE item_id = ' . $this->db->escape($item_id) . ' AND sale_id IS NULL AND receiving_id IS NULL' + 'INSERT INTO ' . $this->db->dbprefix('attribute_links') . ' (item_id, definition_id, attribute_id, ' . $sale_receiving_fk . ') + SELECT ' . $this->db->escape($item_id) . ', definition_id, attribute_id, ' . $this->db->escape($id) . ' + FROM ' . $this->db->dbprefix('attribute_links') . ' + WHERE item_id = ' . $this->db->escape($item_id) . ' AND sale_id IS NULL AND receiving_id IS NULL' ); } public function get_suggestions($definition_id, $term) { - $suggestions = array(); + $suggestions = []; $this->db->distinct(); $this->db->select('attribute_value, attribute_values.attribute_id'); $this->db->from('attribute_definitions AS definition'); @@ -586,25 +617,32 @@ class Attribute extends CI_Model { $this->db->trans_start(); + $locale_date_format = $this->Appconfig->get('dateformat'); + //New Attribute if(empty($attribute_id) || empty($item_id)) { - if(in_array($definition_type, [TEXT, DROPDOWN, CHECKBOX], TRUE)) - { - $attribute_id = $this->value_exists($attribute_value); + //Update attribute_value + $attribute_id = $this->value_exists($attribute_value, $definition_type); - if(empty($attribute_id)) + if($attribute_id === FALSE) + { + switch($definition_type) { - $this->db->insert('attribute_values', array('attribute_value' => $attribute_value)); + case DATE: + $data_type = 'date'; + $attribute_date_value = DateTime::createFromFormat($locale_date_format, $attribute_value); + $attribute_value = $attribute_date_value->format('Y-m-d'); + break; + case DECIMAL: + $data_type = 'decimal'; + break; + default: + $data_type = 'value'; + break; } - } - else if($definition_type == DECIMAL) - { - $this->db->insert('attribute_values', array('attribute_decimal' => $attribute_value)); - } - else - { - $this->db->insert('attribute_values', array('attribute_date' => date('Y-m-d', strtotime($attribute_value)))); + + $this->db->insert('attribute_values', array("attribute_$data_type" => $attribute_value)); } $attribute_id = $attribute_id ? $attribute_id : $this->db->insert_id(); @@ -614,24 +652,26 @@ class Attribute extends CI_Model 'item_id' => empty($item_id) ? NULL : $item_id, 'definition_id' => $definition_id)); } - //Existing Attribute else { - $this->db->where('attribute_id', $attribute_id); + switch($definition_type) + { + case DATE: + $data_type = 'date'; + $attribute_date_value = DateTime::createFromFormat($locale_date_format, $attribute_value); + $attribute_value = $attribute_date_value->format('Y-m-d'); + break; + case DECIMAL: + $data_type = 'decimal'; + break; + default: + $data_type = 'value'; + break; + } - if(in_array($definition_type, [TEXT, DROPDOWN], TRUE)) - { - $this->db->update('attribute_values', array('attribute_value' => $attribute_value)); - } - else if($definition_type == DECIMAL) - { - $this->db->update('attribute_values', array('attribute_decimal' => $attribute_value)); - } - else - { - $this->db->update('attribute_values', array('attribute_date' => date('Y-m-d', strtotime($attribute_value)))); - } + $this->db->where('attribute_id', $attribute_id); + $this->db->update('attribute_values', array("attribute_$data_type" => $attribute_value)); } $this->db->trans_complete(); @@ -641,14 +681,14 @@ class Attribute extends CI_Model public function delete_value($attribute_value, $definition_id) { - return $this->db->query("DELETE atrv, atrl FROM " . $this->db->dbprefix('attribute_values') . " atrv, " . $this->db->dbprefix('attribute_links') . " atrl " . - "WHERE atrl.attribute_id = atrv.attribute_id AND atrv.attribute_value = " . $this->db->escape($attribute_value) . " AND atrl.definition_id = " . $this->db->escape($definition_id)); + return $this->db->query('DELETE atrv, atrl FROM ' . $this->db->dbprefix('attribute_values') . ' atrv, ' . $this->db->dbprefix('attribute_links') . ' atrl ' . + 'WHERE atrl.attribute_id = atrv.attribute_id AND atrv.attribute_value = ' . $this->db->escape($attribute_value) . ' AND atrl.definition_id = ' . $this->db->escape($definition_id)); } /** * Deletes an Attribute definition from the database and associated column in the items_import.csv * - * @param unknown $definition_id Attribute definition ID to remove. + * @param int $definition_id Attribute definition ID to remove. * @return boolean TRUE if successful and FALSE if there is a failure */ public function delete_definition($definition_id) @@ -666,6 +706,33 @@ class Attribute extends CI_Model } /** + * Deletes any attribute_links for a specific definition that do not have an item_id associated with them and are not DROPDOWN types + * + * @param int $definition_id + * @return boolean TRUE is returned if the delete was successful or FALSE if there were any failures + */ + public function delete_orphaned_links($definition_id) + { + $this->db->select('definition_type'); + $this->db->where('definition_id', $definition_id); + + $definition = $this->db->get('attribute_definitions')->row(); + + if($definition->definition_type != DROPDOWN) + { + $this->db->trans_start(); + + $this->db->where('item_id'); + $this->db->where('definition_id', $definition_id); + $this->db->delete('attribute_links'); + + $this->db->trans_complete(); + + return $this->db->trans_status(); + } + } + + /* * Deletes any orphaned values that do not have associated links * @param int $definition_id * @return boolean TRUE is returned if the delete was successful or FALSE if there were any failures @@ -693,6 +760,48 @@ class Attribute extends CI_Model { $this->db->where('definition_id', $definition_id); - return $this->db->update('attribute_definitions', array('deleted'=>0)); + return $this->db->update('attribute_definitions', array('deleted' => 0)); + } + + /** + * + * @param array attributes attributes that need to be fixed + * @param int $definition_id + * @param string $definition_type This dictates what column should be populated in any new attribute_values that are created + */ + public function attribute_cleanup($attributes, $definition_id, $definition_type) + { + $this->db->trans_begin(); + + foreach($attributes as $attribute) + { + $new_attribute_id = $this->save_value($attribute['attribute_value'], $definition_id, FALSE, $attribute['attribute_id'], $definition_type); + + if($this->save_link($attribute['item_id'], $definition_id, $new_attribute_id) == FALSE) + { + log_message('Error', 'Transaction failed'); + $this->db->trans_rollback(); + return FALSE; + } + } + $success = $this->delete_orphaned_links($definition_id); + + $this->db->trans_commit(); + return $success; + } + + /** + * Returns all attribute_ids and item_ids assigned to that definition_id + * + * @param int $definition_id + * @return array All attribute_id and item_id pairs in the attribute_links table with that attribute definition_id + */ + public function get_attributes_by_definition($definition_id) + { + $this->db->select('attribute_links.attribute_id, item_id, attribute_value, attribute_decimal, attribute_date'); + $this->db->join('attribute_values', 'attribute_values.attribute_id = attribute_links.attribute_id'); + $this->db->where('definition_id', $definition_id); + + return $this->db->get('attribute_links')->result_array(); } } \ No newline at end of file diff --git a/application/models/Item.php b/application/models/Item.php index 83528037f..dfd586fb9 100644 --- a/application/models/Item.php +++ b/application/models/Item.php @@ -12,16 +12,15 @@ class Item extends CI_Model { // check if $item_id is a number and not a string starting with 0 // because cases like 00012345 will be seen as a number where it is a barcode - if(ctype_digit($item_id) && substr($item_id, 0, 1) != '0') + if(ctype_digit($item_id) && substr($item_id, 0, 1) !== '0') { - $this->db->from('items'); - $this->db->where('item_id', (int) $item_id); - if($ignore_deleted == FALSE) + $this->db->where('item_id', intval($item_id)); + if($ignore_deleted === FALSE) { $this->db->where('deleted', $deleted); } - return ($this->db->get()->num_rows() == 1); + return ($this->db->get('items')->num_rows() === 1); } return FALSE; @@ -37,16 +36,15 @@ class Item extends CI_Model return FALSE; } - $this->db->from('items'); $this->db->where('item_number', (string) $item_number); // check if $item_id is a number and not a string starting with 0 // because cases like 00012345 will be seen as a number where it is a barcode if(ctype_digit($item_id) && substr($item_id, 0, 1) != '0') { - $this->db->where('item_id !=', (int) $item_id); + $this->db->where('item_id !=', intval($item_id)); } - return $this->db->get()->num_rows() >= 1; + return ($this->db->get('items')->num_rows() >= 1); } /* @@ -82,7 +80,7 @@ class Item extends CI_Model public function search($search, $filters, $rows = 0, $limit_from = 0, $sort = 'items.name', $order = 'asc', $count_only = FALSE) { // get_found_rows case - if($count_only == TRUE) + if($count_only === TRUE) { $this->db->select('COUNT(DISTINCT items.item_id) AS count'); } @@ -208,7 +206,7 @@ class Item extends CI_Model } // get_found_rows case - if($count_only == TRUE) + if($count_only === TRUE) { return $this->db->get()->row()->count; } @@ -262,13 +260,12 @@ class Item extends CI_Model $this->db->select('GROUP_CONCAT(attribute_value SEPARATOR \'|\') AS attribute_values'); $this->db->select('GROUP_CONCAT(attribute_decimal SEPARATOR \'|\') AS attribute_dvalues'); $this->db->select('GROUP_CONCAT(attribute_date SEPARATOR \'|\') AS attribute_dtvalues'); - $this->db->from('items'); $this->db->join('attribute_links', 'attribute_links.item_id = items.item_id', 'left'); $this->db->join('attribute_values', 'attribute_links.attribute_id = attribute_values.attribute_id', 'left'); $this->db->where('items.item_id', $item_id); $this->db->group_by('items.item_id'); - $query = $this->db->get(); + $query = $this->db->get('items'); if($query->num_rows() == 1) { @@ -294,17 +291,14 @@ class Item extends CI_Model */ public function get_info_by_id_or_number($item_id, $include_deleted = TRUE) { - $this->db->from('items'); - $this->db->group_start(); - $this->db->where('items.item_number', $item_id); // check if $item_id is a number and not a string starting with 0 // because cases like 00012345 will be seen as a number where it is a barcode if(ctype_digit($item_id) && substr($item_id, 0, 1) != '0') { - $this->db->or_where('items.item_id', (int) $item_id); + $this->db->or_where('items.item_id', intval($item_id)); } $this->db->group_end(); @@ -318,7 +312,7 @@ class Item extends CI_Model // due to barcode and item_id clash $this->db->limit(1); - $query = $this->db->get(); + $query = $this->db->get('items'); if($query->num_rows() == 1) { @@ -390,10 +384,16 @@ class Item extends CI_Model $this->db->where('item_id', $item_data['item_id']); $this->db->update('items', array('low_sell_item_id'=>$item_data['item_id'])); } + return TRUE; } + return FALSE; } + else + { + $item_data['item_id'] = $item_id; + } $this->db->where('item_id', $item_id); @@ -546,7 +546,7 @@ class Item extends CI_Model public function get_search_suggestions($search, $filters = array('is_deleted' => FALSE, 'search_custom' => FALSE), $unique = FALSE, $limit = 25) { - $suggestions = array(); + $suggestions = []; $non_kit = array(ITEM, ITEM_AMOUNT_ENTRY); $this->db->select($this->get_search_suggestion_format('item_id, name, pack_name')); @@ -614,16 +614,16 @@ class Item extends CI_Model } //Search by custom fields - if($filters['search_custom'] != FALSE) + if($filters['search_custom'] !== FALSE) { - $this->db->from('attribute_links'); - $this->db->join('attribute_values','attribute_links.attribute_id = attribute_values.attribute_id'); + $this->db->join('attribute_values', 'attribute_links.attribute_id = attribute_values.attribute_id'); $this->db->join('attribute_definitions', 'attribute_definitions.definition_id = attribute_links.definition_id'); $this->db->like('attribute_value', $search); $this->db->where('definition_type', TEXT); $this->db->where('deleted', $filters['is_deleted']); + $this->db->where_in('item_type', $non_kit); // standard, exclude kit items since kits will be picked up later - foreach($this->db->get()->result() as $row) + foreach($this->db->get('attribute_links')->result() as $row) { $suggestions[] = array('value' => $row->item_id, 'label' => $this->get_search_suggestion_label($row)); } @@ -642,7 +642,7 @@ class Item extends CI_Model public function get_stock_search_suggestions($search, $filters = array('is_deleted' => FALSE, 'search_custom' => FALSE), $unique = FALSE, $limit = 25) { - $suggestions = array(); + $suggestions = []; $non_kit = array(ITEM, ITEM_AMOUNT_ENTRY); $this->db->select($this->get_search_suggestion_format('item_id, name, pack_name')); @@ -716,16 +716,16 @@ class Item extends CI_Model } //Search by custom fields - if($filters['search_custom'] != FALSE) + if($filters['search_custom'] !== FALSE) { - $this->db->from('attribute_links'); - $this->db->join('attribute_links.attribute_id = attribute_values.attribute_id'); + $this->db->join('attribute_values', 'attribute_links.attribute_id = attribute_values.attribute_id'); $this->db->join('attribute_definitions', 'attribute_definitions.definition_id = attribute_links.definition_id'); $this->db->like('attribute_value', $search); $this->db->where('definition_type', TEXT); $this->db->where('stock_type', '0'); // stocked items only $this->db->where('deleted', $filters['is_deleted']); - foreach($this->db->get()->result() as $row) + + foreach($this->db->get('attribute_links')->result() as $row) { $suggestions[] = array('value' => $row->item_id, 'label' => $this->get_search_suggestion_label($row)); } @@ -743,16 +743,15 @@ class Item extends CI_Model public function get_kit_search_suggestions($search, $filters = array('is_deleted' => FALSE, 'search_custom' => FALSE), $unique = FALSE, $limit = 25) { - $suggestions = array(); + $suggestions = []; $non_kit = array(ITEM, ITEM_AMOUNT_ENTRY); $this->db->select('item_id, name'); - $this->db->from('items'); $this->db->where('deleted', $filters['is_deleted']); $this->db->where('item_type', ITEM_KIT); $this->db->like('name', $search); $this->db->order_by('name', 'asc'); - foreach($this->db->get()->result() as $row) + foreach($this->db->get('items')->result() as $row) { $suggestions[] = array('value' => $row->item_id, 'label' => $row->name); } @@ -778,6 +777,7 @@ class Item extends CI_Model $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); @@ -787,10 +787,12 @@ class Item extends CI_Model $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); @@ -813,31 +815,19 @@ class Item extends CI_Model } //Search by custom fields - if($filters['search_custom'] != FALSE) + if($filters['search_custom'] !== FALSE) { - // This section is currently never used but custom fields are replaced with attributes - // therefore in case this feature is required a proper query needs to be written here - /* - $this->db->from('items'); - $this->db->group_start(); - $this->db->where('item_type', ITEM_KIT); - $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->join('attribute_values', 'attribute_links.attribute_id = attribute_values.attribute_id'); + $this->db->join('attribute_definitions', 'attribute_definitions.definition_id = attribute_links.definition_id'); + $this->db->like('attribute_value', $search); + $this->db->where('definition_type', TEXT); + $this->db->where('stock_type', '0'); // stocked items only $this->db->where('deleted', $filters['is_deleted']); - foreach($this->db->get()->result() as $row) + + foreach($this->db->get('attribute_links')->result() as $row) { - $suggestions[] = array('value' => $row->item_id, 'label' => $row->name); + $suggestions[] = array('value' => $row->item_id, 'label' => $this->get_search_suggestion_label($row)); } - */ } } @@ -852,7 +842,7 @@ class Item extends CI_Model public function get_low_sell_suggestions($search) { - $suggestions = array(); + $suggestions = []; $this->db->select($this->get_search_suggestion_format('item_id, pack_name')); $this->db->from('items'); @@ -870,7 +860,7 @@ class Item extends CI_Model public function get_category_suggestions($search) { - $suggestions = array(); + $suggestions = []; $this->db->distinct(); $this->db->select('category'); $this->db->from('items'); @@ -887,7 +877,7 @@ class Item extends CI_Model public function get_location_suggestions($search) { - $suggestions = array(); + $suggestions = []; $this->db->distinct(); $this->db->select('location'); $this->db->from('items'); diff --git a/application/views/items/form_csv_import.php b/application/views/items/form_csv_import.php index 7c03b0416..c0be42fb4 100644 --- a/application/views/items/form_csv_import.php +++ b/application/views/items/form_csv_import.php @@ -1,10 +1,10 @@ -'csv_form', 'class'=>'form-horizontal')); ?> +'csv_form', 'class'=>'form-horizontal')); ?>