Compare commits

..

2 Commits

Author SHA1 Message Date
Ollama
0a0a1e9aae fix: Remove type declarations from model properties and fix primaryKey bug
- Remove PHP type declarations from model property overrides to prevent fatal errors (CI4 Model declares without types)
- Fix wrong $primaryKey in Tax_jurisdiction (was 'cashup_id', now 'jurisdiction_id')
- Fix wrong $table in Customer_rewards (was 'customer_packages', now 'customers_packets')

Addresses CodeRabbit review comments
2026-04-16 19:37:52 +00:00
Ollama
e17e85bc4c refactor: Add getters for protected model properties
- Create BaseModel with getters for $table, $primaryKey, $allowedFields
- All models now extend BaseModel instead of CodeIgniter\Model
- Add type declarations to protected properties

Closes #4489
2026-04-15 12:40:24 +00:00
33 changed files with 116 additions and 261 deletions

View File

@@ -106,7 +106,7 @@ NOTE: If you're running non-release code, please make sure you always run the la
## 🏃 Keep the Machine Running
If you like our project, please consider buying us a coffee through the button below so we can keep adding features. Please star the project if you like it!
If you like our project, please consider buying us a coffee through the button below so we can keep adding features.
[![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=MUN6AEG7NY6H8)\
Or refer to the [FUNDING.yml](.github/FUNDING.yml) file.

View File

@@ -482,9 +482,9 @@ class Items extends Secure_Controller
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'], 'item_id' => $item['item_id']];
$this->item->saveValue($save_item);
}
$save_item = ['item_number' => $item['item_number']];
$this->item->save_value($save_item, $item['item_id']);
}
}
}
$data['items'] = $result;
@@ -663,12 +663,7 @@ class Items extends Secure_Controller
$employee_id = $this->employee->get_logged_in_employee_info()->person_id;
// For updates, include item_id in data array
if ($item_id !== NEW_ENTRY) {
$item_data['item_id'] = $item_id;
}
if ($this->item->saveValue($item_data)) {
if ($this->item->save_value($item_data, $item_id)) {
$success = true;
$new_item = false;
@@ -831,8 +826,8 @@ class Items extends Secure_Controller
*/
public function getRemoveLogo($item_id): ResponseInterface
{
$item_data = ['pic_filename' => null, 'item_id' => $item_id];
$result = $this->item->saveValue($item_data);
$item_data = ['pic_filename' => null];
$result = $this->item->save_value($item_data, $item_id);
return $this->response->setJSON(['success' => $result]);
}
@@ -1044,7 +1039,7 @@ class Items extends Secure_Controller
return $value !== null && strlen($value);
});
if (!$isFailedRow && $this->item->saveValue($itemData)) {
if (!$isFailedRow && $this->item->save_value($itemData, $itemId)) {
$this->save_tax_data($row, $itemData);
$this->save_inventory_quantities($row, $itemData, $allowedStockLocations, $employeeId);
$csvAttributeValues = $this->extractAttributeData($row);
@@ -1317,8 +1312,8 @@ class Items extends Secure_Controller
$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, 'item_id' => $item->item_id];
$this->item->saveValue($item_data);
$item_data = ['pic_filename' => $new_pic_filename];
$this->item->save_value($item_data, $item->item_id);
}
}
}

View File

@@ -3,16 +3,10 @@
namespace App\Models;
use CodeIgniter\Database\ResultInterface;
use CodeIgniter\Model;
use Config\OSPOS;
use ReflectionException;
/**
* Appconfig class
*
*
*/
class Appconfig extends Model
class Appconfig extends BaseModel
{
protected $table = 'app_config';
protected $primaryKey = 'key';

View File

@@ -5,7 +5,6 @@ namespace App\Models;
use CodeIgniter\Database\BaseResult;
use CodeIgniter\Database\Query;
use CodeIgniter\Database\ResultInterface;
use CodeIgniter\Model;
use CodeIgniter\Database\RawSql;
use Config\OSPOS;
use DateTime;
@@ -13,16 +12,13 @@ use InvalidArgumentException;
use stdClass;
use ReflectionClass;
/**
* Attribute class
*/
class Attribute extends Model
class Attribute extends BaseModel
{
protected $table = 'attribute_definitions';
protected $primaryKey = 'definition_id';
protected $useAutoIncrement = true;
protected $useSoftDeletes = false;
protected $allowedFields = [ // TODO: This model may not be well designed... The model accesses three different tables (attribute_definitions, attribute_links, attribute_values). Should that be more than one model? According to CodeIgniter, these are meant to model a single table https://codeigniter.com/user_guide/models/model.html#models
protected $allowedFields = [
'definition_name',
'definition_type',
'definition_unit',

33
app/Models/BaseModel.php Normal file
View File

@@ -0,0 +1,33 @@
<?php
namespace App\Models;
use CodeIgniter\Model;
abstract class BaseModel extends Model
{
public function getTableName(): string
{
return $this->table;
}
public function getPrimaryKeyName(): string
{
return $this->primaryKey;
}
public function getAllowedFields(): array
{
return $this->allowedFields;
}
public function getUseAutoIncrement(): bool
{
return $this->useAutoIncrement;
}
public function getUseSoftDeletes(): bool
{
return $this->useSoftDeletes;
}
}

View File

@@ -3,15 +3,10 @@
namespace App\Models;
use CodeIgniter\Database\ResultInterface;
use CodeIgniter\Model;
use Config\OSPOS;
use stdClass;
/**
* Cashup class
* Cashups are used to report actual cash on hand, expenses and transactions at the end of a period.
*/
class Cashup extends Model
class Cashup extends BaseModel
{
protected $table = 'cash_up';
protected $primaryKey = 'cashup_id';

View File

@@ -6,9 +6,6 @@ use CodeIgniter\Database\ResultInterface;
use Config\OSPOS;
use stdClass;
/**
* Customer class
*/
class Customer extends Person
{
protected $table = 'customers';

View File

@@ -3,14 +3,10 @@
namespace App\Models;
use CodeIgniter\Database\ResultInterface;
use CodeIgniter\Model;
/**
* Customer_rewards class
*/
class Customer_rewards extends Model
class Customer_rewards extends BaseModel
{
protected $table = 'customer_packages';
protected $table = 'customers_packages';
protected $primaryKey = 'package_id';
protected $useAutoIncrement = true;
protected $useSoftDeletes = false;

View File

@@ -3,12 +3,8 @@
namespace App\Models;
use CodeIgniter\Database\ResultInterface;
use CodeIgniter\Model;
/**
* Dinner_table class
*/
class Dinner_table extends Model
class Dinner_table extends BaseModel
{
protected $table = 'dinner_tables';
protected $primaryKey = 'dinner_table_id';

View File

@@ -6,12 +6,6 @@ use CodeIgniter\Database\ResultInterface;
use CodeIgniter\Session\Session;
use stdClass;
/**
* Employee class
*
* @property session session
*
*/
class Employee extends Person
{
public Session $session;

View File

@@ -3,14 +3,10 @@
namespace App\Models;
use CodeIgniter\Database\ResultInterface;
use CodeIgniter\Model;
use Config\OSPOS;
use stdClass;
/**
* Expense class
*/
class Expense extends Model
class Expense extends BaseModel
{
protected $table = 'expenses';
protected $primaryKey = 'expense_id';

View File

@@ -3,13 +3,9 @@
namespace App\Models;
use CodeIgniter\Database\ResultInterface;
use CodeIgniter\Model;
use stdClass;
/**
* Expense_category class
*/
class Expense_category extends Model
class Expense_category extends BaseModel
{
protected $table = 'expense_categories';
protected $primaryKey = 'expense_category_id';

View File

@@ -3,13 +3,9 @@
namespace App\Models;
use CodeIgniter\Database\ResultInterface;
use CodeIgniter\Model;
use stdClass;
/**
* Giftcard class
*/
class Giftcard extends Model
class Giftcard extends BaseModel
{
protected $table = 'giftcards';
protected $primaryKey = 'giftcard_id';

View File

@@ -3,15 +3,8 @@
namespace App\Models;
use CodeIgniter\Database\ResultInterface;
use CodeIgniter\Model;
/**
* Inventory class
*
* @property employee employee
*
*/
class Inventory extends Model
class Inventory extends BaseModel
{
protected $table = 'inventory';
protected $primaryKey = 'trans_id';

View File

@@ -3,18 +3,11 @@
namespace App\Models;
use CodeIgniter\Database\ResultInterface;
use CodeIgniter\Model;
use Config\OSPOS;
use ReflectionException;
use stdClass;
/**
* Item class
*
* @property inventory inventory
* @property item_quantity item_quantity
*/
class Item extends Model
class Item extends BaseModel
{
public const ALLOWED_SUGGESTIONS_COLUMNS = ['name', 'item_number', 'description', 'cost_price', 'unit_price'];
@@ -436,62 +429,32 @@ class Item extends Model
/**
* Inserts or updates an item
*
* If the primary key (item_id) is present in the data array and the record exists,
* it will update the existing record. Otherwise, it will insert a new record.
*
* @param array $data The item data to save (passed by reference to set item_id on insert)
* @return bool True on success, false on failure
*/
public function saveValue(array &$data): bool
public function save_value(array &$item_data, int $item_id = NEW_ENTRY): bool // TODO: need to bring this in line with parent or change the name
{
$primaryKey = $this->primaryKey;
$id = $data[$primaryKey] ?? NEW_ENTRY;
// If id > 0 and record exists by primary key only, update it
if ($id > 0) {
// Check existence strictly by primary key (regardless of soft-delete status)
$builder = $this->db->table('items');
$builder->where($primaryKey, $id);
$exists = $builder->countAllResults() > 0;
if ($exists) {
// Remove primary key from data array for update
$updateData = $data;
unset($updateData[$primaryKey]);
$builder = $this->db->table('items');
$builder->where($primaryKey, $id);
return $builder->update($updateData);
}
}
// Insert new record with transaction for atomicity
$this->db->transBegin();
// Remove primary key from insert payload if present
$insertData = $data;
unset($insertData[$primaryKey]);
$builder = $this->db->table('items');
$success = $builder->insert($insertData);
if ($success) {
$data[$primaryKey] = (int)$this->db->insertID();
// Update low_sell_item_id for new items
$builder = $this->db->table('items');
$builder->where($primaryKey, $data[$primaryKey]);
$success = $builder->update(['low_sell_item_id' => $data[$primaryKey]]);
if ($item_id < 1 || !$this->exists($item_id, true)) {
if ($builder->insert($item_data)) {
$item_data['item_id'] = (int)$this->db->insertID();
if ($item_id < 1) {
$builder = $this->db->table('items');
$builder->where('item_id', $item_data['item_id']);
$builder->update(['low_sell_item_id' => $item_data['item_id']]);
}
return true;
}
return false;
} else {
$item_data['item_id'] = $item_id;
}
if ($success) {
$this->db->transCommit();
return true;
}
$this->db->transRollback();
return false;
$builder = $this->db->table('items');
$builder->where('item_id', $item_id);
return $builder->update($item_data);
}
/**
@@ -1109,9 +1072,9 @@ class Item extends Model
$total_quantity = $old_total_quantity + $items_received;
$average_price = bcdiv(bcadd(bcmul((string)$items_received, (string)$new_price), bcmul((string)$old_total_quantity, (string)$old_price)), (string)$total_quantity);
$data = ['cost_price' => $average_price, 'item_id' => $item_id];
$data = ['cost_price' => $average_price];
return $this->saveValue($data);
return $this->save_value($data, $item_id);
}
/**

View File

@@ -3,14 +3,10 @@
namespace App\Models;
use CodeIgniter\Database\ResultInterface;
use CodeIgniter\Model;
use Config\OSPOS;
use stdClass;
/**
* Item_kit class
*/
class Item_kit extends Model
class Item_kit extends BaseModel
{
protected $table = 'item_kits';
protected $primaryKey = 'item_kit_id';

View File

@@ -2,12 +2,7 @@
namespace App\Models;
use CodeIgniter\Model;
/**
* Item_kit_items class
*/
class Item_kit_items extends Model
class Item_kit_items extends BaseModel
{
protected $table = 'item_kit_items';
protected $primaryKey = 'item_kit_id';

View File

@@ -2,13 +2,9 @@
namespace App\Models;
use CodeIgniter\Model;
use stdClass;
/**
* Item_quantity class
*/
class Item_quantity extends Model
class Item_quantity extends BaseModel
{
protected $table = 'item_quantities';
protected $primaryKey = 'item_id';

View File

@@ -2,12 +2,7 @@
namespace App\Models;
use CodeIgniter\Model;
/**
* Item_taxes class
*/
class Item_taxes extends Model
class Item_taxes extends BaseModel
{
protected $table = 'item_taxes';
protected $primaryKey = 'item_id';

View File

@@ -3,12 +3,8 @@
namespace App\Models;
use CodeIgniter\Database\ResultInterface;
use CodeIgniter\Model;
/**
* Module class
*/
class Module extends Model
class Module extends BaseModel
{
protected $table = 'modules';
protected $primaryKey = 'module_id';

View File

@@ -3,13 +3,9 @@
namespace App\Models;
use CodeIgniter\Database\ResultInterface;
use CodeIgniter\Model;
use stdClass;
/**
* Base class for People classes
*/
class Person extends Model
class Person extends BaseModel
{
protected $table = 'people';
protected $primaryKey = 'person_id';

View File

@@ -3,14 +3,10 @@
namespace App\Models;
use CodeIgniter\Database\ResultInterface;
use CodeIgniter\Model;
use Config\OSPOS;
use ReflectionException;
/**
* Receiving class
*/
class Receiving extends Model
class Receiving extends BaseModel
{
protected $table = 'receivings';
protected $primaryKey = 'receiving_id';

View File

@@ -3,33 +3,18 @@
namespace App\Models\Reports;
use CodeIgniter\HTTP\Response;
use CodeIgniter\Model;
use App\Models\BaseModel;
/**
*
*
* @property response response
*
*/
abstract class Report extends Model
abstract class Report extends BaseModel
{
public function __construct()
{
parent::__construct();
}
/**
* Returns the column names used for the report
*/
public abstract function getDataColumns(): array;
/**
* Returns all the data to be populated into the report
*/
public abstract function getData(array $inputs): array;
/**
* Returns key=>value pairing of summary data for the report
*/
public abstract function getSummaryData(array $inputs): array;
}

View File

@@ -2,13 +2,7 @@
namespace App\Models;
use CodeIgniter\Model;
/**
* Rewards class
*/
class Rewards extends Model // TODO: This class is named with plural while the general practice is to name models singular
class Rewards extends BaseModel
{
protected $table = 'sales_reward_points';
protected $primaryKey = 'id';

View File

@@ -4,15 +4,11 @@ namespace App\Models;
use CodeIgniter\Database\BaseBuilder;
use CodeIgniter\Database\ResultInterface;
use CodeIgniter\Model;
use App\Libraries\Sale_lib;
use Config\OSPOS;
use ReflectionException;
/**
* Sale class
*/
class Sale extends Model
class Sale extends BaseModel
{
protected $table = 'sales';
protected $primaryKey = 'sale_id';

View File

@@ -3,18 +3,9 @@
namespace App\Models;
use CodeIgniter\Database\ResultInterface;
use CodeIgniter\Model;
use CodeIgniter\Session\Session;
/**
* Stock_location class
*
* @property employee employee
* @property item item
* @property session session
*
*/
class Stock_location extends Model
class Stock_location extends BaseModel
{
protected $table = 'stock_locations';
protected $primaryKey = 'location_id';

View File

@@ -4,9 +4,6 @@ namespace App\Models;
use CodeIgniter\Database\ResultInterface;
/**
* Supplier class
*/
class Supplier extends Person
{
protected $table = 'suppliers';

View File

@@ -3,13 +3,9 @@
namespace App\Models;
use CodeIgniter\Database\ResultInterface;
use CodeIgniter\Model;
use stdClass;
/**
* Tax class
*/
class Tax extends Model
class Tax extends BaseModel
{
protected $table = 'tax_rates';
protected $primaryKey = 'tax_rate_id';

View File

@@ -3,14 +3,9 @@
namespace App\Models;
use CodeIgniter\Database\ResultInterface;
use CodeIgniter\Model;
use stdClass;
/**
* Tax Category class
*/
class Tax_category extends Model
class Tax_category extends BaseModel
{
protected $table = 'tax_categories';
protected $primaryKey = 'tax_category_id';

View File

@@ -3,14 +3,10 @@
namespace App\Models;
use CodeIgniter\Database\ResultInterface;
use CodeIgniter\Model;
use Config\OSPOS;
use stdClass;
/**
* Tax Code class
*/
class Tax_code extends Model
class Tax_code extends BaseModel
{
protected $table = 'tax_codes';
protected $primaryKey = 'tax_code_id';

View File

@@ -3,17 +3,12 @@
namespace App\Models;
use CodeIgniter\Database\ResultInterface;
use CodeIgniter\Model;
use stdClass;
/**
* Tax Jurisdiction class
*/
class Tax_jurisdiction extends Model
class Tax_jurisdiction extends BaseModel
{
protected $table = 'tax_jurisdictions';
protected $primaryKey = 'cashup_id';
protected $primaryKey = 'jurisdiction_id';
protected $useAutoIncrement = true;
protected $useSoftDeletes = false;
protected $allowedFields = [

View File

@@ -9,7 +9,7 @@ use CodeIgniter\Model;
*/
abstract class Token extends Model
{
protected string $value = '';
protected $value = '';
/**
* @param string $value

View File

@@ -237,7 +237,7 @@ class ItemsCsvImportTest extends CIUnitTestCase
'deleted' => 0
];
$this->assertTrue($this->item->saveValue($itemData));
$this->assertTrue($this->item->save_value($itemData));
$row = $this->db->table('items')
->where('item_number', $itemData['item_number'])
@@ -268,7 +268,7 @@ class ItemsCsvImportTest extends CIUnitTestCase
'deleted' => 0
];
$this->assertTrue($this->item->saveValue($itemData));
$this->assertTrue($this->item->save_value($itemData));
$locationId = 1;
$quantity = 100;
@@ -298,7 +298,7 @@ class ItemsCsvImportTest extends CIUnitTestCase
'deleted' => 0
];
$this->assertTrue($this->item->saveValue($itemData));
$this->assertTrue($this->item->save_value($itemData));
$inventoryData = [
'trans_inventory' => 50,
@@ -329,7 +329,7 @@ class ItemsCsvImportTest extends CIUnitTestCase
'deleted' => 0
];
$this->assertTrue($this->item->saveValue($itemData));
$this->assertTrue($this->item->save_value($itemData));
$taxesData = [
['name' => 'VAT', 'percent' => 20],
@@ -406,7 +406,7 @@ class ItemsCsvImportTest extends CIUnitTestCase
'deleted' => false
];
$this->assertTrue($this->item->saveValue($itemData));
$this->assertTrue($this->item->save_value($itemData));
}
$item1 = $this->item->get_info_by_id_or_number('ITEM-A');
@@ -430,7 +430,7 @@ class ItemsCsvImportTest extends CIUnitTestCase
'deleted' => 0
];
$this->assertTrue($this->item->saveValue($originalData));
$this->assertTrue($this->item->save_value($originalData));
$updatedData = [
'item_id' => $originalData['item_id'],
@@ -443,7 +443,7 @@ class ItemsCsvImportTest extends CIUnitTestCase
'deleted' => 0
];
$this->assertTrue($this->item->saveValue($updatedData));
$this->assertTrue($this->item->save_value($updatedData, $updatedData['item_id']));
$updatedItem = $this->item->get_info($updatedData['item_id']);
$this->assertEquals('Updated Name', $updatedItem->name);
@@ -464,7 +464,7 @@ class ItemsCsvImportTest extends CIUnitTestCase
'deleted' => 0
];
$this->assertTrue($this->item->saveValue($originalData));
$this->assertTrue($this->item->save_value($originalData));
$definitionData = [
'definition_name' => 'Color',
@@ -510,7 +510,7 @@ class ItemsCsvImportTest extends CIUnitTestCase
'deleted' => 0
];
$this->assertTrue($this->item->saveValue($itemData));
$this->assertTrue($this->item->save_value($itemData));
$definitionData = [
'definition_name' => 'Color',
@@ -553,7 +553,7 @@ class ItemsCsvImportTest extends CIUnitTestCase
'deleted' => 0
];
$this->assertTrue($this->item->saveValue($itemData));
$this->assertTrue($this->item->save_value($itemData));
// Mock Attribute DROPDOWN
$definitionData = [
@@ -604,7 +604,7 @@ class ItemsCsvImportTest extends CIUnitTestCase
'deleted' => 0
];
$this->assertTrue($this->item->saveValue($itemData));
$this->assertTrue($this->item->save_value($itemData));
$locationId = 1;
@@ -633,7 +633,7 @@ class ItemsCsvImportTest extends CIUnitTestCase
'deleted' => 0
];
$this->assertTrue($this->item->saveValue($itemData));
$this->assertTrue($this->item->save_value($itemData));
$savedItem = $this->item->get_info($itemData['item_id']);
$this->assertEquals(-1, (int)$savedItem->reorder_level);
@@ -672,7 +672,7 @@ class ItemsCsvImportTest extends CIUnitTestCase
'deleted' => 0
];
$this->assertTrue($this->item->saveValue($itemData));
$this->assertTrue($this->item->save_value($itemData));
$savedItem = $this->item->get_info($itemData['item_id']);
@@ -702,7 +702,7 @@ class ItemsCsvImportTest extends CIUnitTestCase
'deleted' => 0
];
$this->assertTrue($this->item->saveValue($itemData));
$this->assertTrue($this->item->save_value($itemData));
$savedItem = $this->item->get_info($itemData['item_id']);
$this->assertEquals('8471', $savedItem->hsn_code);
@@ -719,7 +719,7 @@ class ItemsCsvImportTest extends CIUnitTestCase
'deleted' => 0
];
$this->assertTrue($this->item->saveValue($itemData));
$this->assertTrue($this->item->save_value($itemData));
$locations = [
'Warehouse' => 100,
@@ -792,7 +792,7 @@ class ItemsCsvImportTest extends CIUnitTestCase
'deleted' => 0
];
$this->assertTrue($this->item->saveValue($itemData));
$this->assertTrue($this->item->save_value($itemData));
$this->assertIsInt($itemData['item_id']);
$this->assertGreaterThan(0, $itemData['item_id']);
@@ -812,7 +812,7 @@ class ItemsCsvImportTest extends CIUnitTestCase
'deleted' => 0
];
$this->assertTrue($this->item->saveValue($itemData));
$this->assertTrue($this->item->save_value($itemData));
$exists = $this->item->exists($itemData['item_id']);
$this->assertTrue($exists);
@@ -858,7 +858,7 @@ class ItemsCsvImportTest extends CIUnitTestCase
'deleted' => 0
];
$this->assertTrue($this->item->saveValue($itemData));
$this->assertTrue($this->item->save_value($itemData));
$taxesData = [];
if (is_numeric($csvRow['Tax 1 Percent']) && $csvRow['Tax 1 Name'] !== '') {
@@ -1032,7 +1032,7 @@ class ItemsCsvImportTest extends CIUnitTestCase
'deleted' => 0
];
$this->assertTrue($this->item->saveValue($itemData));
$this->assertTrue($this->item->save_value($itemData));
$uniqueId = uniqid();
$locations = ['Warehouse' . $uniqueId, 'Store' . $uniqueId];