Skip to content

refactor: Unify Item model save_value signature - #4510

Open
jekkos wants to merge 4 commits into
masterfrom
issue-4459
Open

refactor: Unify Item model save_value signature#4510
jekkos wants to merge 4 commits into
masterfrom
issue-4459

Conversation

@jekkos

@jekkos jekkos commented Apr 15, 2026

Copy link
Copy Markdown
Member

Summary

  • Rename save_value() to saveValue() for PSR compliance
  • Remove second parameter (item_id) - now derived from data array
  • Check for item_id in data to determine insert vs update
  • Update all call sites in Items controller
  • Update test file references

Problem

Model save_value() functions had inconsistent signatures. Some took the primary key as an optional second parameter, others required it. Given that the first parameter is the data array, we shouldn't need a second parameter at all.

Solution

This PR focuses on the Item model as a representative subset:

  1. Changed save_value() to saveValue() (PSR naming)
  2. Removed second parameter - now derives item_id from data array
  3. Logic: if item_id > 0 and exists → UPDATE, else → INSERT
  4. Updated all call sites in Items.php controller
  5. Updated test file references

Part of #4459

Ultraworked with Sisyphus

Co-authored-by: Sisyphus clio-agent@sisyphuslabs.ai

Summary by CodeRabbit

  • Refactor
    • Consolidated item create/update persistence so the record identifier is always included in the saved payload, making saves and updates consistent.
  • Tests
    • Updated automated tests to match the consolidated save/update behavior.
  • Chores
    • Simplified internal persistence logic and improved reliability of inserts/updates.

Note: No user-facing behavior or public APIs changed.

- Rename save_value() to saveValue() for PSR compliance
- Remove second parameter (item_id) - now derived from data array
- Check for item_id in data to determine insert vs update
- Update all call sites in Items controller
- Update test file references

Part of #4459
@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4c629341-a2da-444c-bccc-515774301e06

📥 Commits

Reviewing files that changed from the base of the PR and between a4c0d08 and 196d1e4.

📒 Files selected for processing (1)
  • app/Models/Item.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/Models/Item.php

📝 Walkthrough

Walkthrough

Controller, model, and tests were updated to use a renamed upsert method saveValue(array $data) that derives item_id from the payload instead of accepting it as a separate parameter; controllers now inject item_id into data before calling the model.

Changes

Cohort / File(s) Summary
Controller updates
app/Controllers/Items.php
Replaced calls to save_value($data, $item_id) with saveValue($data). Controllers now insert item_id into the payload for create/update paths including barcode generation, logo removal, CSV import, and pic filename updates.
Model rename & upsert logic
app/Models/Item.php
Renamed save_value(array &$item_data, int $item_id = NEW_ENTRY)saveValue(array &$data). Upsert reads ID from $data[$this->primaryKey], removes PK from payload for writes, performs inserts inside a DB transaction (sets PK from insertID()), and updates low_sell_item_id post-insert. change_cost_price() updated to include item_id and call saveValue().
Tests updated
tests/Controllers/ItemsCsvImportTest.php
All test calls updated from $this->item->save_value(...) to $this->item->saveValue(...), converting two-argument usages to the single-argument payload form.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

enhancement

Poem

🐰 I hopped through code with gentle paws tonight,
Tucked item_id in payload, snug and right.
A name changed, a tidy hop, a cheerful shove —
Data and DB now fit like a glove. 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: refactoring the Item model's save_value method signature to unify how item_id is passed (from a separate parameter to within the data array), including renaming to PSR-compliant saveValue().

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-4459

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/Models/Item.php (1)

446-472: Refactored saveValue method looks good overall.

The method correctly derives item_id from the data array and implements upsert logic. A few observations:

  1. When performing an UPDATE (line 455), the $data array may contain item_id, which is not in $allowedFields. CodeIgniter's Query Builder should filter this out, but it's cleaner to explicitly exclude the primary key from the update payload.

  2. On line 452, exists($id, true) with ignore_deleted=true means updates will target even soft-deleted items. Verify this is the intended behavior.

♻️ Optional: Exclude primary key from update data
         // If id > 0 and record exists, update it
         if ($id > 0 && $this->exists($id, true)) {
             $builder = $this->db->table('items');
             $builder->where($primaryKey, $id);
+            $updateData = array_diff_key($data, [$primaryKey => true]);
-            return $builder->update($data);
+            return $builder->update($updateData);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Models/Item.php` around lines 446 - 472, The saveValue method may send
the primary key in the update payload and currently calls exists($id, true)
which includes soft-deleted records; before updating in saveValue, remove the
primary key key (use $this->primaryKey) from the $data array to ensure only
allowed fields are updated (so the Query Builder doesn't receive item_id), and
confirm whether using exists($id, true) (the second param that ignores deleted
state) is intended—if not, change to exists($id) or pass false; also ensure the
low_sell_item_id update logic still uses the inserted ID from
$this->db->insertID().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/Controllers/Items.php`:
- Around line 483-487: The code saves an empty item_number because it never
generates a barcode when $item['item_number'] is empty despite
$this->config['barcode_generate_if_empty'] being true; fix by generating a new
barcode value before saving: inside the same conditional that checks
isset($item['item_number']) && empty($item['item_number']) &&
$this->config['barcode_generate_if_empty'], call or implement a barcode
generator (e.g. a new helper or an existing method like
$this->item->generateBarcode or BarcodeGenerator::generate) to produce a
non-empty value, assign that value to $save_item['item_number'], then call
$this->item->saveValue($save_item) (keeping the isset($item['item_id']) guard),
and ensure the generated barcode is validated/unique before persisting.

---

Nitpick comments:
In `@app/Models/Item.php`:
- Around line 446-472: The saveValue method may send the primary key in the
update payload and currently calls exists($id, true) which includes soft-deleted
records; before updating in saveValue, remove the primary key key (use
$this->primaryKey) from the $data array to ensure only allowed fields are
updated (so the Query Builder doesn't receive item_id), and confirm whether
using exists($id, true) (the second param that ignores deleted state) is
intended—if not, change to exists($id) or pass false; also ensure the
low_sell_item_id update logic still uses the inserted ID from
$this->db->insertID().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e15e8752-0103-4c7f-9482-9199509e3d3a

📥 Commits

Reviewing files that changed from the base of the PR and between 165c335 and baf135d.

📒 Files selected for processing (3)
  • app/Controllers/Items.php
  • app/Models/Item.php
  • tests/Controllers/ItemsCsvImportTest.php

Comment thread app/Controllers/Items.php
Comment on lines 483 to +487
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']);
}
$save_item = ['item_number' => $item['item_number'], 'item_id' => $item['item_id']];
$this->item->saveValue($save_item);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if there's barcode generation logic that should be invoked here
rg -n -C5 'barcode_generate_if_empty' --type=php

Repository: opensourcepos/opensourcepos

Length of output: 50383


🏁 Script executed:

sed -n '480,495p' app/Controllers/Items.php

Repository: opensourcepos/opensourcepos

Length of output: 666


🏁 Script executed:

# Search for barcode generation functions
rg -n 'generate.*barcode|barcode.*generate' --type=php -i

Repository: opensourcepos/opensourcepos

Length of output: 18109


🏁 Script executed:

# Look for functions in Barcode_lib
rg -n 'function|public|private' app/Libraries/Barcode_lib.php | head -20

Repository: opensourcepos/opensourcepos

Length of output: 841


🏁 Script executed:

# Read the generate_barcode function in Barcode_lib
sed -n '107,125p' app/Libraries/Barcode_lib.php

Repository: opensourcepos/opensourcepos

Length of output: 698


🏁 Script executed:

# Read the get_barcode_value and display_barcode functions
sed -n '95,165p' app/Libraries/Barcode_lib.php

Repository: opensourcepos/opensourcepos

Length of output: 2859


🏁 Script executed:

# Check the Item model saveValue method
rg -n 'saveValue|function saveValue' app/Models/Item.php -A 10

Repository: opensourcepos/opensourcepos

Length of output: 845


🏁 Script executed:

# Look for barcode generation functions (UUID, sequence, etc.)
rg -n 'generate.*barcode|uuid|barcode.*id|barcode.*number' app/Models/Item.php -i

Repository: opensourcepos/opensourcepos

Length of output: 118


🏁 Script executed:

# Get full context of getGenerateBarcodes function
sed -n '470,495p' app/Controllers/Items.php

Repository: opensourcepos/opensourcepos

Length of output: 1338


Logic error: Saving empty item_number value.

The condition on line 483 checks if $item['item_number'] is empty, but line 485 assigns that same empty value to $save_item['item_number']. The code should generate a new barcode value when the item lacks one and barcode_generate_if_empty is enabled, but the barcode generation logic is missing entirely. Currently, it only persists the empty value back to the database.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Controllers/Items.php` around lines 483 - 487, The code saves an empty
item_number because it never generates a barcode when $item['item_number'] is
empty despite $this->config['barcode_generate_if_empty'] being true; fix by
generating a new barcode value before saving: inside the same conditional that
checks isset($item['item_number']) && empty($item['item_number']) &&
$this->config['barcode_generate_if_empty'], call or implement a barcode
generator (e.g. a new helper or an existing method like
$this->item->generateBarcode or BarcodeGenerator::generate) to produce a
non-empty value, assign that value to $save_item['item_number'], then call
$this->item->saveValue($save_item) (keeping the isset($item['item_id']) guard),
and ensure the generated barcode is validated/unique before persisting.

- Unset item_id from data array before update
- Cleaner approach to avoid including PK in update payload

Address CodeRabbit review feedback

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/Models/Item.php`:
- Around line 462-472: Wrap the insert + low_sell_item_id update in a DB
transaction and return success only if both operations succeed: start a
transaction via $this->db->transStart()/transComplete() (or
transBegin()/transCommit()/transRollback()), perform $builder->insert($data) and
capture $this->db->insertID(), then perform the follow-up
$builder->update(['low_sell_item_id' => $data[$primaryKey]]) and check its
boolean result; on failure roll back (or rely on transStatus()) and return
false, otherwise commit and return true. Ensure you reference the same $builder,
$primaryKey and $this->db->insertID() values used currently so the update
targets the newly inserted row.
- Around line 449-453: The update branch is being chosen when non-primary unique
fields (like item_number/barcode) match because the code uses exists($id, true)
which can match by other identifiers; change the decision to a primary-key-only
check so only an explicit primary key triggers UPDATE. Replace the exists($id,
true) usage in the block that evaluates $id = $data[$primaryKey] ?? NEW_ENTRY
with a call that verifies existence strictly by primary key (e.g. add/use an
existsByPrimaryKey($id) or adjust exists() to accept a primary-only flag) and
use that instead to decide the update path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dbebb5ef-afb3-4fea-b36c-d8ba73f67394

📥 Commits

Reviewing files that changed from the base of the PR and between baf135d and e7daa7a.

📒 Files selected for processing (1)
  • app/Models/Item.php

Comment thread app/Models/Item.php Outdated
Comment thread app/Models/Item.php Outdated
- Replace exists() with direct primary key check to avoid matching by other identifiers
- Wrap insert + low_sell_item_id update in transaction for atomicity
- Check db insert result and rollback on failure

Address CodeRabbit review feedback

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/Models/Item.php`:
- Around line 451-457: The existence check in the Item model's upsert logic
erroneously excludes soft-deleted rows by adding ->where('deleted', 0), causing
records with a valid primary key ($primaryKey / $id) to be treated as not found;
remove the ->where('deleted', 0) condition (or otherwise ensure the existence
check only filters by $primaryKey and $id using the $builder on table 'items')
so $exists correctly reflects any record with that primary key regardless of
soft-delete status, allowing the code that relies on $exists to choose UPDATE
rather than INSERT.
- Around line 449-475: The fallback insert path currently inserts
caller-supplied primary key from $data (when $id > 0 but update isn't taken);
before calling $builder->insert($data) remove the primary key from the insert
payload (e.g. unset $data[$primaryKey] or create $insertData = $data and unset
$insertData[$primaryKey]) and use that sanitized payload for the insert, keeping
the existing transaction logic ($this->db->transBegin() / insert /
commit/rollback) intact; reference $primaryKey, $data, NEW_ENTRY, and the
$builder->insert(...) call to locate the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 36d35ecd-b68d-46e0-b781-bff18e5c4e82

📥 Commits

Reviewing files that changed from the base of the PR and between e7daa7a and a4c0d08.

📒 Files selected for processing (1)
  • app/Models/Item.php

Comment thread app/Models/Item.php
Comment thread app/Models/Item.php
- Remove deleted=0 filter from existence check (allow soft-deleted updates)
- Remove primary key from insert payload to avoid conflicts
- Cleaner approach for upsert logic

Address CodeRabbit review feedback
@jekkos
jekkos requested a review from objecttothis April 16, 2026 05:37
Comment thread app/Controllers/Items.php

if ($this->item->save_value($item_data, $item_id)) {
// For updates, include item_id in data array
if ($item_id !== NEW_ENTRY) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's helpful if we refactor local variables as we touch them to be PSR compliant. Not mandatory but helpful.

Comment thread app/Controllers/Items.php
$item_data['item_id'] = $item_id;
}

if ($this->item->saveValue($item_data)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

same with $item_data

Comment thread app/Controllers/Items.php
{
$item_data = ['pic_filename' => null];
$result = $this->item->save_value($item_data, $item_id);
$item_data = ['pic_filename' => null, 'item_id' => $item_id];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Helpful but not required: refactor local variables to PSR compliant naming.

Comment thread app/Controllers/Items.php
$item_data = ['pic_filename' => $new_pic_filename];
$this->item->save_value($item_data, $item->item_id);
$item_data = ['pic_filename' => $new_pic_filename, 'item_id' => $item->item_id];
$this->item->saveValue($item_data);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Helpful but not required: refactor local variables you touch here to PSR compliant names.

Comment thread app/Models/Item.php
$builder->update(['low_sell_item_id' => $item_data['item_id']]);
}
$primaryKey = $this->primaryKey;
$id = $data[$primaryKey] ?? NEW_ENTRY;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since we are dealing with items.item_id this variable might be better named as $itemId instead of the generic $id.

Comment thread app/Models/Item.php
$id = $data[$primaryKey] ?? NEW_ENTRY;

return true;
// If id > 0 and record exists by primary key only, update it

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Replace this comment with // Update

Comment thread app/Models/Item.php
$item_data['item_id'] = $item_id;
}

// Insert new record with transaction for atomicity

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Replace this comment with // Insert

Comment thread app/Models/Item.php
$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];
$data = ['cost_price' => $average_price, 'item_id' => $item_id];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not required but good: refactor $item_id in this function to PSR compliant code

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.

2 participants