mirror of
https://github.com/opensourcepos/opensourcepos.git
synced 2025-12-24 01:57:51 -05:00
* Improve code style and PSR-12 compliance - refactored code formatting to adhere to PSR-12 guidelines - standardized coding conventions across the codebase - added missing framework files and reverted markup changes - reformatted arrays for enhanced readability - updated language files for consistent styling and clarity - minor miscellaneous improvements
1319 lines
53 KiB
PHP
1319 lines
53 KiB
PHP
<?php
|
|
|
|
namespace App\Controllers;
|
|
|
|
use App\Libraries\Barcode_lib;
|
|
use App\Libraries\Item_lib;
|
|
|
|
use App\Models\Attribute;
|
|
use App\Models\Inventory;
|
|
use App\Models\Item;
|
|
use App\Models\Item_kit;
|
|
use App\Models\Item_quantity;
|
|
use App\Models\Item_taxes;
|
|
use App\Models\Stock_location;
|
|
use App\Models\Supplier;
|
|
use App\Models\Tax_category;
|
|
|
|
use CodeIgniter\Images\Handlers\BaseHandler;
|
|
use CodeIgniter\HTTP\DownloadResponse;
|
|
use Config\OSPOS;
|
|
use Config\Services;
|
|
use ReflectionException;
|
|
|
|
require_once('Secure_Controller.php');
|
|
|
|
class Items extends Secure_Controller
|
|
{
|
|
private BaseHandler $image;
|
|
private Barcode_lib $barcode_lib;
|
|
private Item_lib $item_lib;
|
|
private Attribute $attribute;
|
|
private Inventory $inventory;
|
|
private Item $item;
|
|
private Item_kit $item_kit;
|
|
private Item_quantity $item_quantity;
|
|
private Item_taxes $item_taxes;
|
|
private Stock_location $stock_location;
|
|
private Supplier $supplier;
|
|
private Tax_category $tax_category;
|
|
private array $config;
|
|
|
|
|
|
public function __construct()
|
|
{
|
|
parent::__construct('items');
|
|
|
|
$this->session = Services::session();
|
|
|
|
$this->image = Services::image();
|
|
|
|
$this->barcode_lib = new Barcode_lib();
|
|
$this->item_lib = new Item_lib();
|
|
|
|
$this->attribute = model(Attribute::class);
|
|
$this->inventory = model(Inventory::class);
|
|
$this->item = model(Item::class);
|
|
$this->item_kit = model(Item_kit::class);
|
|
$this->item_quantity = model(Item_quantity::class);
|
|
$this->item_taxes = model(Item_taxes::class);
|
|
$this->stock_location = model(Stock_location::class);
|
|
$this->supplier = model(Supplier::class);
|
|
$this->tax_category = model(Tax_category::class);
|
|
$this->config = config(OSPOS::class)->settings;
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function getIndex(): void
|
|
{
|
|
$this->session->set('allow_temp_items', 0);
|
|
|
|
$data['table_headers'] = get_items_manage_table_headers();
|
|
$data['stock_location'] = $this->item_lib->get_item_location();
|
|
$data['stock_locations'] = $this->stock_location->get_allowed_locations();
|
|
|
|
// Filters that will be loaded in the multiselect dropdown
|
|
$data['filters'] = [
|
|
'empty_upc' => lang('Items.empty_upc_items'),
|
|
'low_inventory' => lang('Items.low_inventory_items'),
|
|
'is_serialized' => lang('Items.serialized_items'),
|
|
'no_description' => lang('Items.no_description_items'),
|
|
'search_custom' => lang('Items.search_attributes'),
|
|
'is_deleted' => lang('Items.is_deleted'),
|
|
'temporary' => lang('Items.temp')
|
|
];
|
|
|
|
echo view('items/manage', $data);
|
|
}
|
|
|
|
/**
|
|
* Returns Items table data rows. This will be called with AJAX.
|
|
* @noinspection PhpUnused
|
|
**/
|
|
public function getSearch(): void
|
|
{
|
|
$search = $this->request->getGet('search');
|
|
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
|
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
|
$sort = $this->sanitizeSortColumn(item_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'item_id');
|
|
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
|
|
|
$this->item_lib->set_item_location($this->request->getGet('stock_location'));
|
|
|
|
$definition_names = $this->attribute->get_definitions_by_flags(Attribute::SHOW_IN_ITEMS);
|
|
|
|
$filters = [
|
|
'start_date' => $this->request->getGet('start_date'),
|
|
'end_date' => $this->request->getGet('end_date'),
|
|
'stock_location_id' => $this->item_lib->get_item_location(),
|
|
'empty_upc' => false,
|
|
'low_inventory' => false,
|
|
'is_serialized' => false,
|
|
'no_description' => false,
|
|
'search_custom' => false,
|
|
'is_deleted' => false,
|
|
'temporary' => false,
|
|
'definition_ids' => array_keys($definition_names)
|
|
];
|
|
|
|
// Check if any filter is set in the multiselect dropdown
|
|
$request_filters = array_fill_keys($this->request->getGet('filters', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? [], true);
|
|
$filters = array_merge($filters, $request_filters);
|
|
$items = $this->item->search($search, $filters, $limit, $offset, $sort, $order);
|
|
$total_rows = $this->item->get_found_rows($search, $filters);
|
|
$data_rows = [];
|
|
|
|
foreach ($items->getResult() as $item) {
|
|
$data_rows[] = get_item_data_row($item);
|
|
|
|
if ($item->pic_filename !== null) {
|
|
$this->update_pic_filename($item);
|
|
}
|
|
}
|
|
|
|
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
|
|
}
|
|
|
|
/**
|
|
* AJAX function. Processes thumbnail of image. Called via tabular_helper
|
|
* @param string $pic_filename
|
|
* @return void
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getPicThumb(string $pic_filename): void
|
|
{
|
|
helper('file');
|
|
|
|
$file_extension = pathinfo($pic_filename, PATHINFO_EXTENSION);
|
|
$images = glob("./uploads/item_pics/$pic_filename");
|
|
$base_path = './uploads/item_pics/' . pathinfo($pic_filename, PATHINFO_FILENAME);
|
|
|
|
if (sizeof($images) > 0) {
|
|
$image_path = $images[0];
|
|
$thumb_path = $base_path . "_thumb.$file_extension";
|
|
|
|
if (sizeof($images) < 2 && !file_exists($thumb_path)) {
|
|
$image = Services::image('gd2');
|
|
$image->withFile($image_path)
|
|
->resize(52, 32, true, 'height')
|
|
->save($thumb_path);
|
|
}
|
|
|
|
$this->response->setContentType(mime_content_type($thumb_path));
|
|
$this->response->setBody(file_get_contents($thumb_path));
|
|
$this->response->send();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gives search suggestions based on what is being searched for
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function suggest_search(): void
|
|
{
|
|
$options = [
|
|
'search_custom' => $this->request->getPost('search_custom'),
|
|
'is_deleted' => $this->request->getPost('is_deleted') !== null
|
|
];
|
|
|
|
$search = $this->request->getPost('term');
|
|
$suggestions = $this->item->get_search_suggestions($search, $options);
|
|
|
|
echo json_encode($suggestions);
|
|
}
|
|
|
|
/**
|
|
* AJAX Function used to get search suggestions from the model and return them in JSON format
|
|
* @return void
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getSuggest(): void
|
|
{
|
|
$search = $this->request->getGet('term');
|
|
$suggestions = $this->item->get_search_suggestions($search, ['search_custom' => false, 'is_deleted' => false], true);
|
|
|
|
echo json_encode($suggestions);
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getSuggestLowSell(): void
|
|
{
|
|
$suggestions = $this->item->get_low_sell_suggestions($this->request->getPostGet('name'));
|
|
|
|
echo json_encode($suggestions);
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getSuggestKits(): void
|
|
{
|
|
$suggestions = $this->item->get_kit_search_suggestions($this->request->getGet('term'), ['search_custom' => false, 'is_deleted' => false], true);
|
|
|
|
echo json_encode($suggestions);
|
|
}
|
|
|
|
/**
|
|
* Gives search suggestions based on what is being searched for. Called from the view.
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getSuggestCategory(): void
|
|
{
|
|
$suggestions = $this->item->get_category_suggestions($this->request->getGet('term'));
|
|
|
|
echo json_encode($suggestions);
|
|
}
|
|
|
|
/**
|
|
* Gives search suggestions based on what is being searched for.
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getSuggestLocation(): void
|
|
{
|
|
$suggestions = $this->item->get_location_suggestions($this->request->getGet('term'));
|
|
|
|
echo json_encode($suggestions);
|
|
}
|
|
|
|
/**
|
|
* @param string $item_ids
|
|
* @return void
|
|
*/
|
|
public function getRow(string $item_ids): void // TODO: An array would be better for parameter.
|
|
{
|
|
$item_infos = $this->item->get_multiple_info(explode(':', $item_ids), $this->item_lib->get_item_location());
|
|
|
|
$result = [];
|
|
|
|
foreach ($item_infos->getResult() as $item_info) {
|
|
$result[$item_info->item_id] = get_item_data_row($item_info);
|
|
}
|
|
|
|
echo json_encode($result);
|
|
}
|
|
|
|
/**
|
|
* @param int $item_id
|
|
* @return void
|
|
*/
|
|
public function getView(int $item_id = NEW_ENTRY): void // TODO: Long function. Perhaps we need to refactor out some methods.
|
|
{
|
|
$item_id ??= NEW_ENTRY;
|
|
|
|
if ($item_id === NEW_ENTRY) {
|
|
$data = [];
|
|
}
|
|
|
|
$data['allow_temp_item'] = $this->session->get('allow_temp_items'); // allow_temp_items is set in the index function of items.php or sales.php
|
|
$data['item_tax_info'] = $this->item_taxes->get_info($item_id);
|
|
$data['default_tax_1_rate'] = '';
|
|
$data['default_tax_2_rate'] = '';
|
|
$data['item_kit_disabled'] = !$this->employee->has_grant('item_kits', $this->employee->get_logged_in_employee_info()->person_id);
|
|
$data['definition_values'] = $this->attribute->get_attributes_by_item($item_id);
|
|
$data['definition_names'] = $this->attribute->get_definition_names();
|
|
|
|
foreach ($data['definition_values'] as $definition_id => $definition) {
|
|
unset($data['definition_names'][$definition_id]);
|
|
}
|
|
|
|
$item_info = $this->item->get_info($item_id);
|
|
|
|
$data['allow_temp_item'] = ($data['allow_temp_item'] === 1 && $item_id !== NEW_ENTRY && $item_info->item_type != ITEM_TEMP) ? 0 : 1;
|
|
|
|
$use_destination_based_tax = (bool)$this->config['use_destination_based_tax'];
|
|
$data['include_hsn'] = $this->config['include_hsn'] === '1';
|
|
$data['category_dropdown'] = $this->config['category_dropdown'];
|
|
|
|
if ($data['category_dropdown'] === '1') {
|
|
$categories = ['' => lang('Items.none')];
|
|
$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 === NEW_ENTRY) {
|
|
$data['default_tax_1_rate'] = $this->config['default_tax_1_rate'];
|
|
$data['default_tax_2_rate'] = $this->config['default_tax_2_rate'];
|
|
|
|
$item_info->receiving_quantity = 1;
|
|
$item_info->reorder_level = 1;
|
|
$item_info->item_type = ITEM; // Standard
|
|
$item_info->item_id = $item_id;
|
|
$item_info->stock_type = HAS_STOCK;
|
|
$item_info->tax_category_id = null;
|
|
$item_info->qty_per_pack = 1;
|
|
$item_info->pack_name = lang('Items.default_pack_name');
|
|
|
|
if ($use_destination_based_tax) {
|
|
$item_info->tax_category_id = $this->config['default_tax_category'];
|
|
}
|
|
}
|
|
|
|
$data['standard_item_locked'] = (
|
|
$data['item_kit_disabled']
|
|
&& $item_info->item_type == ITEM_KIT
|
|
&& !$data['allow_temp_item']
|
|
&& !($this->config['derive_sale_quantity'] === '1')
|
|
);
|
|
|
|
$data['item_info'] = $item_info;
|
|
|
|
$suppliers = ['' => lang('Items.none')];
|
|
|
|
foreach ($this->supplier->get_all()->getResultArray() as $row) {
|
|
$suppliers[$row['person_id']] = $row['company_name'];
|
|
}
|
|
|
|
$data['suppliers'] = $suppliers;
|
|
$data['selected_supplier'] = $item_info->supplier_id;
|
|
|
|
$data['hsn_code'] = $data['include_hsn']
|
|
? $item_info->hsn_code
|
|
: '';
|
|
|
|
if ($use_destination_based_tax) {
|
|
$data['use_destination_based_tax'] = true;
|
|
$tax_categories = [];
|
|
|
|
foreach ($this->tax_category->get_all()->getResultArray() as $row) {
|
|
$tax_categories[$row['tax_category_id']] = $row['tax_category'];
|
|
}
|
|
|
|
$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;
|
|
} else {
|
|
$data['use_destination_based_tax'] = false;
|
|
$data['tax_categories'] = [];
|
|
$data['tax_category'] = '';
|
|
}
|
|
|
|
$data['logo_exists'] = $item_info->pic_filename !== null;
|
|
if ($item_info->pic_filename != null) {
|
|
$file_extension = pathinfo($item_info->pic_filename, PATHINFO_EXTENSION);
|
|
if (empty($file_extension)) {
|
|
$images = glob("./uploads/item_pics/$item_info->pic_filename.*");
|
|
} else {
|
|
$images = glob("./uploads/item_pics/$item_info->pic_filename");
|
|
}
|
|
$data['image_path'] = sizeof($images) > 0 ? base_url($images[0]) : '';
|
|
} else {
|
|
$data['image_path'] = '';
|
|
}
|
|
|
|
$stock_locations = $this->stock_location->get_undeleted_all()->getResultArray();
|
|
|
|
foreach ($stock_locations as $location) {
|
|
$quantity = $this->item_quantity->get_item_quantity($item_id, $location['location_id'])->quantity;
|
|
$quantity = ($item_id === NEW_ENTRY) ? 0 : $quantity;
|
|
$location_array[$location['location_id']] = ['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 !== NEW_ENTRY && $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, [$low_sell_item_info->name, $low_sell_item_info->pack_name]);
|
|
} else {
|
|
$data['selected_low_sell_item'] = '';
|
|
}
|
|
|
|
echo view('items/form', $data);
|
|
}
|
|
|
|
/**
|
|
* AJAX called function which returns the update inventory form view for an item
|
|
*
|
|
* @param int $item_id
|
|
* @return void
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getInventory(int $item_id = NEW_ENTRY): void
|
|
{
|
|
$item_info = $this->item->get_info($item_id); // TODO: Duplicate code
|
|
|
|
foreach (get_object_vars($item_info) as $property => $value) {
|
|
$item_info->$property = $value;
|
|
}
|
|
|
|
$data['item_info'] = $item_info;
|
|
$data['stock_locations'] = [];
|
|
$stock_locations = $this->stock_location->get_undeleted_all()->getResultArray();
|
|
|
|
foreach ($stock_locations as $location) {
|
|
$quantity = $this->item_quantity->get_item_quantity($item_id, $location['location_id'])->quantity;
|
|
|
|
$data['stock_locations'][$location['location_id']] = $location['location_name'];
|
|
$data['item_quantities'][$location['location_id']] = $quantity;
|
|
}
|
|
|
|
echo view('items/form_inventory', $data);
|
|
}
|
|
|
|
/**
|
|
* @param int $item_id
|
|
* @return void
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getCountDetails(int $item_id = NEW_ENTRY): void
|
|
{
|
|
$item_info = $this->item->get_info($item_id); // TODO: Duplicate code
|
|
|
|
foreach (get_object_vars($item_info) as $property => $value) {
|
|
$item_info->$property = $value;
|
|
}
|
|
|
|
$data['item_info'] = $item_info;
|
|
$data['stock_locations'] = [];
|
|
$stock_locations = $this->stock_location->get_undeleted_all()->getResultArray();
|
|
|
|
foreach ($stock_locations as $location) {
|
|
$quantity = $this->item_quantity->get_item_quantity($item_id, $location['location_id'])->quantity;
|
|
|
|
$data['stock_locations'][$location['location_id']] = $location['location_name'];
|
|
$data['item_quantities'][$location['location_id']] = $quantity;
|
|
}
|
|
|
|
echo view('items/form_count_details', $data);
|
|
}
|
|
|
|
/**
|
|
* AJAX called function that generates barcodes for selected items.
|
|
*
|
|
* @param string $item_ids Colon separated list of item_id values to generate barcodes for.
|
|
* @return void
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getGenerateBarcodes(string $item_ids): void // TODO: Passing these through as a string instead of an array limits the contents of the item_ids. Perhaps a better approach would to serialize as JSON in an array and pass through post variables?
|
|
{
|
|
$item_ids = explode(':', $item_ids);
|
|
$result = $this->item->get_multiple_info($item_ids, $this->item_lib->get_item_location())->getResultArray();
|
|
$data['barcode_config'] = $this->barcode_lib->get_barcode_config();
|
|
|
|
foreach ($result as &$item) {
|
|
if (isset($item['item_number']) && empty($item['item_number']) && $this->config['barcode_generate_if_empty']) {
|
|
if (isset($item['item_id'])) {
|
|
$save_item = ['item_number' => $item['item_number']];
|
|
$this->item->save_value($save_item, $item['item_id']);
|
|
}
|
|
}
|
|
}
|
|
$data['items'] = $result;
|
|
|
|
echo view('barcodes/barcode_sheet', $data);
|
|
}
|
|
|
|
/**
|
|
* Gathers attribute value information for an item and returns it in a view.
|
|
*
|
|
* @param int $item_id
|
|
* @return void
|
|
*/
|
|
public function getAttributes(int $item_id = NEW_ENTRY): void
|
|
{
|
|
$data['item_id'] = $item_id;
|
|
$definition_ids = json_decode($this->request->getGet('definition_ids') ?? '', true);
|
|
$data['definition_values'] = $this->attribute->get_attributes_by_item($item_id) + $this->attribute->get_values_by_definitions($definition_ids);
|
|
$data['definition_names'] = $this->attribute->get_definition_names();
|
|
|
|
foreach ($data['definition_values'] as $definition_id => $definition_value) {
|
|
$attribute_value = $this->attribute->get_attribute_value($item_id, $definition_id);
|
|
$attribute_id = (empty($attribute_value) || empty($attribute_value->attribute_id)) ? null : $attribute_value->attribute_id;
|
|
$values = &$data['definition_values'][$definition_id];
|
|
$values['attribute_id'] = $attribute_id;
|
|
$values['attribute_value'] = $attribute_value;
|
|
$values['selected_value'] = '';
|
|
|
|
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);
|
|
$values['selected_value'] = (empty($link_value)) ? '' : $link_value->attribute_id;
|
|
}
|
|
|
|
if (!empty($definition_ids[$definition_id])) {
|
|
$values['selected_value'] = $definition_ids[$definition_id];
|
|
}
|
|
|
|
unset($data['definition_names'][$definition_id]);
|
|
}
|
|
|
|
echo view('attributes/item', $data);
|
|
}
|
|
|
|
/**
|
|
* @param int $item_id
|
|
* @return void
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function postAttributes(int $item_id = NEW_ENTRY): void
|
|
{
|
|
$data['item_id'] = $item_id;
|
|
$definition_ids = json_decode($this->request->getPost('definition_ids'), true);
|
|
$data['definition_values'] = $this->attribute->get_attributes_by_item($item_id) + $this->attribute->get_values_by_definitions($definition_ids);
|
|
$data['definition_names'] = $this->attribute->get_definition_names();
|
|
|
|
foreach ($data['definition_values'] as $definition_id => $definition_value) {
|
|
$attribute_value = $this->attribute->get_attribute_value($item_id, $definition_id);
|
|
$attribute_id = (empty($attribute_value) || empty($attribute_value->attribute_id)) ? null : $attribute_value->attribute_id;
|
|
$values = &$data['definition_values'][$definition_id];
|
|
$values['attribute_id'] = $attribute_id;
|
|
$values['attribute_value'] = $attribute_value;
|
|
$values['selected_value'] = '';
|
|
|
|
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);
|
|
$values['selected_value'] = (empty($link_value)) ? '' : $link_value->attribute_id;
|
|
}
|
|
|
|
if (!empty($definition_ids[$definition_id])) {
|
|
$values['selected_value'] = $definition_ids[$definition_id];
|
|
}
|
|
|
|
unset($data['definition_names'][$definition_id]);
|
|
}
|
|
|
|
echo view('attributes/item', $data);
|
|
}
|
|
|
|
/**
|
|
* Edit multiple items. Used in app/Views/items/manage.php
|
|
*
|
|
* @return void
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getBulkEdit(): void
|
|
{
|
|
$suppliers = ['' => lang('Items.none')];
|
|
|
|
foreach ($this->supplier->get_all()->getResultArray() as $row) {
|
|
$suppliers[$row['person_id']] = $row['company_name'];
|
|
}
|
|
|
|
$data['suppliers'] = $suppliers;
|
|
$data['allow_alt_description_choices'] = [
|
|
'' => lang('Items.do_nothing'),
|
|
1 => lang('Items.change_all_to_allow_alt_desc'),
|
|
0 => lang('Items.change_all_to_not_allow_allow_desc')
|
|
];
|
|
|
|
$data['serialization_choices'] = [
|
|
'' => lang('Items.do_nothing'),
|
|
1 => lang('Items.change_all_to_serialized'),
|
|
0 => lang('Items.change_all_to_unserialized')
|
|
];
|
|
|
|
echo view('items/form_bulk', $data);
|
|
}
|
|
|
|
/**
|
|
* @param int $item_id
|
|
* @throws ReflectionException
|
|
*/
|
|
public function postSave(int $item_id = NEW_ENTRY): void
|
|
{
|
|
$upload_data = $this->upload_image();
|
|
$upload_success = empty($upload_data['error']);
|
|
|
|
$raw_receiving_quantity = $this->request->getPost('receiving_quantity');
|
|
|
|
$receiving_quantity = parse_quantity($raw_receiving_quantity);
|
|
$item_type = $this->request->getPost('item_type') === null ? ITEM : intval($this->request->getPost('item_type'));
|
|
|
|
if ($receiving_quantity === 0.0 && $item_type !== ITEM_TEMP) {
|
|
$receiving_quantity = 1;
|
|
}
|
|
|
|
$default_pack_name = lang('Items.default_pack_name');
|
|
|
|
$cost_price = parse_decimals($this->request->getPost('cost_price'));
|
|
$unit_price = parse_decimals($this->request->getPost('unit_price'));
|
|
$reorder_level = parse_quantity($this->request->getPost('reorder_level'));
|
|
$qty_per_pack = parse_quantity($this->request->getPost('qty_per_pack') ?? '');
|
|
|
|
// Save item data
|
|
$item_data = [
|
|
'name' => $this->request->getPost('name'),
|
|
'description' => $this->request->getPost('description'),
|
|
'category' => $this->request->getPost('category'),
|
|
'item_type' => $item_type,
|
|
'stock_type' => $this->request->getPost('stock_type') === null ? HAS_STOCK : intval($this->request->getPost('stock_type')),
|
|
'supplier_id' => empty($this->request->getPost('supplier_id')) ? null : intval($this->request->getPost('supplier_id')),
|
|
'item_number' => empty($this->request->getPost('item_number')) ? null : $this->request->getPost('item_number'),
|
|
'cost_price' => $cost_price,
|
|
'unit_price' => $unit_price,
|
|
'reorder_level' => $reorder_level,
|
|
'receiving_quantity' => $receiving_quantity,
|
|
'allow_alt_description' => $this->request->getPost('allow_alt_description') != null,
|
|
'is_serialized' => $this->request->getPost('is_serialized') != null,
|
|
'qty_per_pack' => $this->request->getPost('qty_per_pack') == null ? 1 : parse_quantity($qty_per_pack),
|
|
'pack_name' => $this->request->getPost('pack_name') == null ? $default_pack_name : $this->request->getPost('pack_name'),
|
|
'low_sell_item_id' => $this->request->getPost('low_sell_item_id') === null ? $item_id : intval($this->request->getPost('low_sell_item_id')),
|
|
'deleted' => $this->request->getPost('is_deleted') != null,
|
|
'hsn_code' => $this->request->getPost('hsn_code') === null ? '' : $this->request->getPost('hsn_code')
|
|
];
|
|
|
|
if ($item_data['item_type'] == ITEM_TEMP) {
|
|
$item_data['stock_type'] = HAS_NO_STOCK;
|
|
$item_data['receiving_quantity'] = 0;
|
|
$item_data['reorder_level'] = 0;
|
|
}
|
|
|
|
$tax_category_id = intval($this->request->getPost('tax_category_id'));
|
|
|
|
if (!isset($tax_category_id)) {
|
|
$item_data['tax_category_id'] = '';
|
|
} else {
|
|
$item_data['tax_category_id'] = empty($this->request->getPost('tax_category_id')) ? null : intval($this->request->getPost('tax_category_id'));
|
|
}
|
|
|
|
if (!empty($upload_data['orig_name']) && $upload_data['raw_name']) {
|
|
$item_data['pic_filename'] = $upload_data['raw_name'] . '.' . $upload_data['file_ext'];
|
|
}
|
|
|
|
$employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
|
|
|
if ($this->item->save_value($item_data, $item_id)) {
|
|
$success = true;
|
|
$new_item = false;
|
|
|
|
if ($item_id === NEW_ENTRY) {
|
|
$item_id = $item_data['item_id'];
|
|
$new_item = true;
|
|
}
|
|
|
|
$use_destination_based_tax = (bool)$this->config['use_destination_based_tax'];
|
|
|
|
if (!$use_destination_based_tax) {
|
|
$items_taxes_data = [];
|
|
$tax_names = $this->request->getPost('tax_names');
|
|
$tax_percents = $this->request->getPost('tax_percents');
|
|
|
|
$tax_name_index = 0;
|
|
|
|
foreach ($tax_percents as $tax_percent) {
|
|
$tax_percentage = parse_tax($tax_percent);
|
|
|
|
if (is_numeric($tax_percentage)) {
|
|
$items_taxes_data[] = ['name' => $tax_names[$tax_name_index], 'percent' => $tax_percentage];
|
|
}
|
|
|
|
$tax_name_index++;
|
|
}
|
|
$success &= $this->item_taxes->save_value($items_taxes_data, $item_id);
|
|
}
|
|
|
|
// Save item quantity
|
|
$stock_locations = $this->stock_location->get_undeleted_all()->getResultArray();
|
|
foreach ($stock_locations as $location) {
|
|
$updated_quantity = parse_quantity($this->request->getPost('quantity_' . $location['location_id']));
|
|
|
|
if ($item_data['item_type'] == ITEM_TEMP) {
|
|
$updated_quantity = 0;
|
|
}
|
|
|
|
$location_detail = [
|
|
'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']);
|
|
|
|
if ($item_quantity->quantity != $updated_quantity || $new_item) {
|
|
$success &= $this->item_quantity->save_value($location_detail, $item_id, $location['location_id']);
|
|
|
|
$inv_data = [
|
|
'trans_date' => date('Y-m-d H:i:s'),
|
|
'trans_items' => $item_id,
|
|
'trans_user' => $employee_id,
|
|
'trans_location' => $location['location_id'],
|
|
'trans_comment' => lang('Items.manually_editing_of_quantity'),
|
|
'trans_inventory' => $updated_quantity - $item_quantity->quantity
|
|
];
|
|
|
|
$success &= $this->inventory->insert($inv_data, false);
|
|
}
|
|
}
|
|
$this->saveItemAttributes($item_id);
|
|
|
|
if ($success && $upload_success) {
|
|
$message = lang('Items.successful_' . ($new_item ? 'adding' : 'updating')) . ' ' . $item_data['name'];
|
|
|
|
echo json_encode(['success' => true, 'message' => $message, 'id' => $item_id]);
|
|
} else {
|
|
$message = $upload_success ? lang('Items.error_adding_updating') . ' ' . $item_data['name'] : strip_tags($upload_data['error']);
|
|
|
|
echo json_encode(['success' => false, 'message' => $message, 'id' => $item_id]);
|
|
}
|
|
} else {
|
|
$message = lang('Items.error_adding_updating') . ' ' . $item_data['name'];
|
|
|
|
echo json_encode(['success' => false, 'message' => $message, 'id' => NEW_ENTRY]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Let files be uploaded with their original name
|
|
* @return array
|
|
*/
|
|
private function upload_image(): array
|
|
{
|
|
$file = $this->request->getFile('items_image');
|
|
if (!$file) {
|
|
return [];
|
|
}
|
|
|
|
helper(['form']);
|
|
$validation_rule = [
|
|
'items_image' => [
|
|
'label' => 'Item Image',
|
|
'rules' => [
|
|
'uploaded[items_image]',
|
|
'is_image[items_image]',
|
|
'max_size[items_image,' . $this->config['image_max_size'] . ']',
|
|
'mime_in[items_image,image/png,image/jpg,image/jpeg,image/gif]',
|
|
'ext_in[items_image,' . $this->config['image_allowed_types'] . ']',
|
|
'max_dims[items_image,' . $this->config['image_max_width'] . ',' . $this->config['image_max_height'] . ']'
|
|
]
|
|
]
|
|
];
|
|
|
|
if (!$this->validate($validation_rule)) {
|
|
return (['error' => $this->validator->getError('items_image')]);
|
|
}
|
|
|
|
$filename = $file->getClientName();
|
|
$info = pathinfo($filename);
|
|
|
|
$file_info = [
|
|
'orig_name' => $filename,
|
|
'raw_name' => $info['filename'],
|
|
'file_ext' => $file->guessExtension()
|
|
];
|
|
|
|
$file->move(FCPATH . 'uploads/item_pics/', $file_info['raw_name'] . '.' . $file_info['file_ext'], true);
|
|
return ($file_info);
|
|
}
|
|
|
|
|
|
/**
|
|
* Ajax call to check to see if the item number, a.k.a. barcode, is already used by another item
|
|
* If it exists then that is an error condition so return true for "error found"
|
|
* @return void
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function postCheckItemNumber(): void
|
|
{
|
|
$exists = $this->item->item_number_exists($this->request->getPost('item_number'), $this->request->getPost('item_id'));
|
|
|
|
echo !$exists ? 'true' : 'false';
|
|
}
|
|
|
|
/**
|
|
* Checks to see if an item kit with the same name as the item already exists.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function check_kit_exists(): void // TODO: This function appears to be never called in the code. Need to confirm.
|
|
{
|
|
if ($this->request->getPost('item_number') === NEW_ENTRY) {
|
|
$exists = $this->item_kit->item_kit_exists_for_name($this->request->getPost('name')); // TODO: item_kit_exists_for_name doesn't exist in Item_kit. I looked at the blame and it appears to have never existed.
|
|
} else {
|
|
$exists = false;
|
|
}
|
|
echo !$exists ? 'true' : 'false';
|
|
}
|
|
|
|
/**
|
|
* @param $item_id
|
|
* @return void
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getRemoveLogo($item_id): void
|
|
{
|
|
$item_data = ['pic_filename' => null];
|
|
$result = $this->item->save_value($item_data, $item_id);
|
|
|
|
echo json_encode(['success' => $result]);
|
|
}
|
|
|
|
/**
|
|
* @throws ReflectionException
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function postSaveInventory($item_id = NEW_ENTRY): void
|
|
{
|
|
$employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
|
$cur_item_info = $this->item->get_info($item_id);
|
|
$location_id = $this->request->getPost('stock_location');
|
|
$new_quantity = $this->request->getPost('newquantity');
|
|
$inv_data = [
|
|
'trans_date' => date('Y-m-d H:i:s'),
|
|
'trans_items' => $item_id,
|
|
'trans_user' => $employee_id,
|
|
'trans_location' => $location_id,
|
|
'trans_comment' => $this->request->getPost('trans_comment'),
|
|
'trans_inventory' => parse_quantity($new_quantity)
|
|
];
|
|
|
|
$this->inventory->insert($inv_data, false);
|
|
|
|
// Update stock quantity
|
|
$item_quantity = $this->item_quantity->get_item_quantity($item_id, $location_id);
|
|
$item_quantity_data = [
|
|
'item_id' => $item_id,
|
|
'location_id' => $location_id,
|
|
'quantity' => $item_quantity->quantity + parse_quantity($this->request->getPost('newquantity'))
|
|
];
|
|
|
|
if ($this->item_quantity->save_value($item_quantity_data, $item_id, $location_id)) {
|
|
$message = lang('Items.successful_updating') . " $cur_item_info->name";
|
|
|
|
echo json_encode(['success' => true, 'message' => $message, 'id' => $item_id]);
|
|
} else {
|
|
$message = lang('Items.error_adding_updating') . " $cur_item_info->name";
|
|
|
|
echo json_encode(['success' => false, 'message' => $message, 'id' => NEW_ENTRY]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function postBulkUpdate(): void
|
|
{
|
|
$items_to_update = $this->request->getPost('item_ids');
|
|
$item_data = [];
|
|
|
|
foreach ($_POST as $key => $value) {
|
|
// This field is nullable, so treat it differently
|
|
if ($key === 'supplier_id' && $value !== '') {
|
|
$item_data[$key] = $value;
|
|
} elseif ($value !== '' && !(in_array($key, ['item_ids', 'tax_names', 'tax_percents']))) {
|
|
$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 = [];
|
|
$tax_names = $this->request->getPost('tax_names');
|
|
$tax_percents = $this->request->getPost('tax_percents');
|
|
$tax_updated = false;
|
|
|
|
foreach ($tax_percents as $tax_percent) {
|
|
if (!empty($tax_names[$tax_percent]) && is_numeric($tax_percents[$tax_percent])) {
|
|
$tax_updated = true;
|
|
$items_taxes_data[] = ['name' => $tax_names[$tax_percent], 'percent' => $tax_percents[$tax_percent]];
|
|
}
|
|
}
|
|
|
|
if ($tax_updated) {
|
|
$this->item_taxes->save_multiple($items_taxes_data, $items_to_update);
|
|
}
|
|
|
|
echo json_encode(['success' => true, 'message' => lang('Items.successful_bulk_edit'), 'id' => $items_to_update]);
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => lang('Items.error_updating_multiple')]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*/
|
|
public function postDelete(): void
|
|
{
|
|
$items_to_delete = $this->request->getPost('ids');
|
|
|
|
if ($this->item->delete_list($items_to_delete)) {
|
|
$message = lang('Items.successful_deleted') . ' ' . count($items_to_delete) . ' ' . lang('Items.one_or_multiple');
|
|
echo json_encode(['success' => true, 'message' => $message]);
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => lang('Items.cannot_be_deleted')]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generates a template CSV file for item import/update containing headers for current stock locations and attributes. Used in app/Views/items/form_csv_import.php
|
|
*
|
|
* @return DownloadResponse
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getGenerateCsvFile(): DownloadResponse
|
|
{
|
|
helper('importfile_helper');
|
|
$name = 'import_items.csv';
|
|
$allowed_locations = $this->stock_location->get_allowed_locations();
|
|
$allowed_attributes = $this->attribute->get_definition_names();
|
|
$data = generate_import_items_csv($allowed_locations, $allowed_attributes);
|
|
|
|
return $this->response->download($name, $data);
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getCsvImport(): void
|
|
{
|
|
echo view('items/form_csv_import');
|
|
}
|
|
|
|
/**
|
|
* Imports items from CSV formatted file.
|
|
* @throws ReflectionException
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function postImportCsvFile(): void
|
|
{
|
|
helper('importfile_helper');
|
|
if ($_FILES['file_path']['error'] !== UPLOAD_ERR_OK) {
|
|
echo json_encode(['success' => false, 'message' => lang('Items.csv_import_failed')]);
|
|
} else {
|
|
if (file_exists($_FILES['file_path']['tmp_name'])) {
|
|
set_time_limit(240);
|
|
|
|
$failCodes = [];
|
|
$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[NEW_ENTRY]); // Removes the common_none_selected_text from the array
|
|
|
|
$attribute_data = [];
|
|
|
|
foreach ($attribute_definition_names as $definition_name) {
|
|
$attribute_data[$definition_name] = $this->attribute->get_definition_by_name($definition_name)[0];
|
|
|
|
if ($attribute_data[$definition_name]['definition_type'] === DROPDOWN) {
|
|
$attribute_data[$definition_name]['dropdown_values'] = $this->attribute->get_definition_values($attribute_data[$definition_name]['definition_id']);
|
|
}
|
|
}
|
|
$db = db_connect();
|
|
$db->transBegin(); // TODO: This section needs to be reworked so that the data array is being created then passed to the Item model because $db doesn't exist in the controller without being instantiated, but database operations should be restricted to the model
|
|
|
|
foreach ($csv_rows as $key => $row) {
|
|
$is_failed_row = false;
|
|
$item_id = (int)$row['Id'];
|
|
$is_update = ($item_id > 0);
|
|
$item_data = [
|
|
'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']) && !$is_update) {
|
|
$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);
|
|
}
|
|
|
|
// Remove false, null, '' and empty strings but keep 0
|
|
$item_data = array_filter($item_data, function ($value) {
|
|
return $value !== null && strlen($value);
|
|
});
|
|
|
|
if (!$is_failed_row && $this->item->save_value($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); // TODO: $is_failed_row never gets used after this.
|
|
|
|
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 = lang('Items.csv_import_partially_failed', [count($failCodes), implode(', ', $failCodes)]);
|
|
$db->transRollback();
|
|
echo json_encode(['success' => false, 'message' => $message]);
|
|
} else {
|
|
$db->transCommit();
|
|
|
|
echo json_encode(['success' => true, 'message' => lang('Items.csv_import_success')]);
|
|
}
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => lang('Items.csv_import_nodata_wrongformat')]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks the entire line of data in an import file for errors
|
|
*
|
|
* @param array $row
|
|
* @param array $item_data
|
|
* @param array $allowed_locations
|
|
* @param array $definition_names
|
|
* @param array $attribute_data
|
|
* @return bool Returns false if all data checks out and true when there is an error in the data
|
|
*/
|
|
private function data_error_check(array $row, array $item_data, array $allowed_locations, array $definition_names, array $attribute_data): bool // TODO: Long function and large number of parameters in the declaration... perhaps refactoring is needed
|
|
{
|
|
$item_id = $row['Id'];
|
|
$is_update = (bool)$item_id;
|
|
|
|
// Check for empty required fields
|
|
$check_for_empty = [
|
|
'name' => $item_data['name'],
|
|
'category' => $item_data['category'],
|
|
'unit_price' => $item_data['unit_price']
|
|
];
|
|
|
|
foreach ($check_for_empty as $key => $val) {
|
|
if (empty($val) && !$is_update) {
|
|
log_message('error', "Empty required value in $key.");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
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 = [
|
|
'cost_price' => $item_data['cost_price'],
|
|
'unit_price' => $item_data['unit_price'],
|
|
'reorder_level' => $item_data['reorder_level'],
|
|
'supplier_id' => $row['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($row["attribute_$definition_name"])) {
|
|
$definition_type = $attribute_data[$definition_name]['definition_type'];
|
|
$attribute_value = $row["attribute_$definition_name"];
|
|
|
|
switch ($definition_type) {
|
|
case DROPDOWN:
|
|
$dropdown_values = $attribute_data[$definition_name]['dropdown_values'];
|
|
$dropdown_values[] = '';
|
|
|
|
if (!empty($attribute_value) && !in_array($attribute_value, $dropdown_values)) {
|
|
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) && !empty($attribute_value)) {
|
|
log_message('error', "'$attribute_value' is not an acceptable DATE value. The value must match the set locale.");
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Saves attribute data found in the CSV import.
|
|
*
|
|
* @param array $row
|
|
* @param array $item_data
|
|
* @param array $definitions
|
|
* @return bool
|
|
*/
|
|
private function save_attribute_data(array $row, array $item_data, array $definitions): bool
|
|
{
|
|
foreach ($definitions as $definition) {
|
|
$attribute_name = $definition['definition_name'];
|
|
$attribute_value = $row["attribute_$attribute_name"];
|
|
|
|
// Create attribute value
|
|
if (!empty($attribute_value) || $attribute_value === '0') {
|
|
if ($definition['definition_type'] === CHECKBOX) {
|
|
$checkbox_is_unchecked = (strcasecmp($attribute_value, 'false') === 0 || $attribute_value === '0');
|
|
$attribute_value = $checkbox_is_unchecked ? '0' : '1';
|
|
|
|
$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;
|
|
}
|
|
|
|
if (!$attribute_id) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Saves the attribute_value and attribute_link if necessary
|
|
*/
|
|
private function store_attribute_value(string $value, array $attribute_data, int $item_id)
|
|
{
|
|
$attribute_id = $this->attribute->attributeValueExists($value, $attribute_data['definition_type']);
|
|
|
|
$this->attribute->deleteAttributeLinks($item_id, $attribute_data['definition_id']);
|
|
|
|
if (!$attribute_id) {
|
|
$attribute_id = $this->attribute->saveAttributeValue($value, $attribute_data['definition_id'], $item_id, false, $attribute_data['definition_type']);
|
|
} elseif (!$this->attribute->saveAttributeLink($item_id, $attribute_data['definition_id'], $attribute_id)) {
|
|
return false;
|
|
}
|
|
|
|
return $attribute_id;
|
|
}
|
|
|
|
/**
|
|
* Saves inventory quantities for the row in the appropriate stock locations.
|
|
*
|
|
* @param array $row
|
|
* @param array $item_data
|
|
* @param array $allowed_locations
|
|
* @param int $employee_id
|
|
* @throws ReflectionException
|
|
*/
|
|
private function save_inventory_quantities(array $row, array $item_data, array $allowed_locations, int $employee_id): void
|
|
{
|
|
// Quantities & Inventory Section
|
|
$comment = lang('Items.inventory_CSV_import_quantity');
|
|
$is_update = (bool)$row['Id'];
|
|
|
|
foreach ($allowed_locations as $location_id => $location_name) {
|
|
$item_quantity_data = ['item_id' => $item_data['item_id'], 'location_id' => $location_id];
|
|
|
|
$csv_data = [
|
|
'trans_items' => $item_data['item_id'],
|
|
'trans_user' => $employee_id,
|
|
'trans_comment' => $comment,
|
|
'trans_location' => $location_id
|
|
];
|
|
|
|
if (!empty($row["location_$location_name"]) || $row["location_$location_name"] === '0') {
|
|
$item_quantity_data['quantity'] = $row["location_$location_name"];
|
|
$this->item_quantity->save_value($item_quantity_data, $item_data['item_id'], $location_id);
|
|
|
|
$csv_data['trans_inventory'] = $row["location_$location_name"];
|
|
$this->inventory->insert($csv_data, false);
|
|
} elseif ($is_update) {
|
|
return;
|
|
} else {
|
|
$item_quantity_data['quantity'] = 0;
|
|
$this->item_quantity->save_value($item_quantity_data, $item_data['item_id'], $location_id);
|
|
|
|
$csv_data['trans_inventory'] = 0;
|
|
$this->inventory->insert($csv_data, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Saves the tax data found in the line of the CSV items import file
|
|
*
|
|
* @param array $row
|
|
* @param array $item_data
|
|
*/
|
|
private function save_tax_data(array $row, array $item_data): void
|
|
{
|
|
$items_taxes_data = [];
|
|
|
|
if (is_numeric($row['Tax 1 Percent']) && $row['Tax 1 Name'] !== '') {
|
|
$items_taxes_data[] = ['name' => $row['Tax 1 Name'], 'percent' => $row['Tax 1 Percent']];
|
|
}
|
|
|
|
if (is_numeric($row['Tax 2 Percent']) && $row['Tax 2 Name'] !== '') {
|
|
$items_taxes_data[] = ['name' => $row['Tax 2 Name'], 'percent' => $row['Tax 2 Percent']];
|
|
}
|
|
|
|
if (isset($items_taxes_data)) {
|
|
$this->item_taxes->save_value($items_taxes_data, $item_data['item_id']);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 object item to update
|
|
*/
|
|
private function update_pic_filename(object $item): void
|
|
{
|
|
$filename = pathinfo($item->pic_filename, PATHINFO_FILENAME);
|
|
|
|
// 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(FCPATH . "uploads/item_pics/$item->pic_filename.*");
|
|
if (sizeof($images) > 0) {
|
|
$new_pic_filename = pathinfo($images[0], PATHINFO_BASENAME);
|
|
$item_data = ['pic_filename' => $new_pic_filename];
|
|
$this->item->save_value($item_data, $item->item_id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Saves item attributes for a given item.
|
|
*
|
|
* @param int $itemId The item for which attributes need to be saved to.
|
|
* @return void
|
|
*/
|
|
public function saveItemAttributes(int $itemId): void
|
|
{
|
|
$attributeLinks = $this->request->getPost('attribute_links') ?? [];
|
|
$attributeIds = $this->request->getPost('attribute_ids');
|
|
|
|
$this->attribute->deleteAttributeLinks($itemId);
|
|
|
|
foreach ($attributeLinks as $definitionId => $attributeValue) {
|
|
$definitionType = $this->attribute->getAttributeInfo($definitionId)->definition_type;
|
|
|
|
switch ($definitionType) {
|
|
case DROPDOWN:
|
|
$attributeId = $attributeValue;
|
|
break;
|
|
case DECIMAL:
|
|
$attributeValue = parse_decimals($attributeValue);
|
|
// Fall through to save the attribute value
|
|
default:
|
|
$attributeId = $this->attribute->saveAttributeValue($attributeValue, $definitionId, $itemId, $attributeIds[$definitionId], $definitionType);
|
|
break;
|
|
}
|
|
|
|
$this->attribute->saveAttributeLink($itemId, $definitionId, $attributeId);
|
|
}
|
|
}
|
|
}
|