Skip to content

Add read-only inventory permission (view stock & selling price only)#4358

Open
henri1024 wants to merge 1 commit intoopensourcepos:masterfrom
henri1024:feature/view-stock-only
Open

Add read-only inventory permission (view stock & selling price only)#4358
henri1024 wants to merge 1 commit intoopensourcepos:masterfrom
henri1024:feature/view-stock-only

Conversation

@henri1024
Copy link

🧾 Description

This PR introduces a new read-only inventory permission that allows specific users (e.g. shop intern) to:

  • View current stock quantity
  • View selling price

while restricting access to:

  • Product cost / purchase price
  • Inventory creation, update, or deletion
  • Any inventory-related configuration

This feature is intended for retail shops where clerks should be able to sell items and check stock availability without being able to modify inventory data or see sensitive cost information.


✨ Key Changes

  • Added a new permission role: items_view
  • Inventory module now checks permission before:
    • Displaying product cost fields
    • Allowing create / edit / delete actions
  • UI hides cost-related columns when permission is not granted
  • Selling price and stock remain visible for authorized users
  • Backend validation added to prevent unauthorized access via direct URL or API calls

🔐 Permission Behavior

Action Admin Shop Clerk (view_inventory_basic)
View stock
View selling price
View product cost
Add / edit product
Delete product

🎯 Use Case

  • Small to medium retail shops
  • Clear separation between owner/admin and shop clerk roles
  • Prevents accidental or intentional inventory changes
  • Protects sensitive cost and margin information

🧪 Testing

  • Tested with multiple user roles
  • Verified UI behavior and direct URL access
  • Confirmed product cost is not exposed in inventory views or reports

📎 Notes

  • Backward compatible
  • Existing installations are unaffected unless the new permission is explicitly assigned

@jekkos jekkos requested a review from objecttothis December 27, 2025 14:11
@jekkos jekkos self-assigned this Dec 27, 2025
Copy link
Member

@objecttothis objecttothis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take a look at my comments and let me know what you think.

/**
* Check if user has items_stock permission
*/
private function has_stock_permission(): bool
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These functions seem like they could be used by other controllers. Maybe it is better to place them into something like Security_helper.php or a common place so that it can be used globally.

*/
public function getInventory(int $item_id = NEW_ENTRY): void
{
// Check permission: only users with stock permission can update inventory
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment probably isn't necessary. The if statement makes it pretty clear what the code is doing.

*/
public function postSave(int $item_id = NEW_ENTRY): void
{
// Check if user has items_stock permission
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

*/
public function postSaveInventory($item_id = NEW_ENTRY): void
{
// Check permission: only users with stock permission can update inventory
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here and below.


use CodeIgniter\Database\Migration;

class Migration_add_items_view_permission extends Migration
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we enable the linter, it's not going to like this class naming.


INSERT INTO `ospos_permissions` (`permission_id`, `module_id`, `location_id`) VALUES
('items_stock', 'items', 1),
('items_view', 'items', 1),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this file is frozen and this needs to be done through a migration.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct indeed, migration alone is sufficient

$item_taxes = model(Item_taxes::class);
$tax_category = model(Tax_category::class);
$config = config(OSPOS::class)->settings;
$has_stock_permission = $permissions['has_stock_permission'] ?? true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably should use PSR-12 compliant naming so that we are constantly moving toward PSR-12 compliance rather than away from it. $hasStockPermission.

$icons = [];

// Only show edit button if user has stock permission
if ($has_stock_permission) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't all three of these be placed under just one if ($has_stock_permission) { check?

* @param string $location_name
* @return void
*/
private function _insert_new_subpermission(string $permission_id, int $location_id, string $location_name): void
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since PHP has function scoping (since PHP 5.0), hungarian notation to notate private functions is obsolete. I think this is why we added the TODO on line 189. I don't think we should add new functions with hungarian notation, but instead move to a PSR compliant naming for the function and for parameters. private function insertNewSubpermission(string $permissionId, int $locationId, string $locationName): void

use App\Models\Employee;

$employee = model(Employee::class);
$logged_in_employee = $employee->get_logged_in_employee_info();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's make these variable names PSR compliant.

$this->db->table('ospos_permissions')->insert($permission_data);
log_message('info', 'Inserted items_view permission.');

// Grant items_view permission to all existing employees
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@objecttothis should we grant this permission to an employee if she does not have access to the items module? I would also add this condition.

$permission_data = [
'permission_id' => 'items_view',
'module_id' => 'items',
'location_id' => 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should insert this data for all current active location ids. Please check if we can get rid of the hardcoded id here by iterating over active stock locations for an employee and generate the data for each of them.


// Mask cost_price for view-only users
if (!$this->has_stock_permission()) {
$item_info->cost_price = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope this can't lead to accidental cost price updates ? I guess if this condition holds then the user will not be able to post back the values?

@jekkos
Copy link
Member

jekkos commented Dec 30, 2025

Thanks for this useful addition, hope you can get the comments worked out so we can merge it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants