Skip to content

Add UPC support to product import, search, and duplicate scan flow#1468

Open
mch987 wants to merge 1 commit into
sysadminsmedia:mainfrom
mch987:codex/add-upc-functionality-to-product-database
Open

Add UPC support to product import, search, and duplicate scan flow#1468
mch987 wants to merge 1 commit into
sysadminsmedia:mainfrom
mch987:codex/add-upc-functionality-to-product-database

Conversation

@mch987
Copy link
Copy Markdown

@mch987 mch987 commented Apr 27, 2026

Motivation

Allow UPC/barcode to be stored and used when importing products so scanned barcodes can prefill items and detect duplicates.
Make UPC searchable so users can find items by barcode from the main item search flow.
Surface UPC in the UI (create modal, edit form and import modal) to improve scanning/import UX.

Description

Added upc to the entity API types and DTOs used by the frontend (EntityCreate, EntityUpdate, EntityOut, EntitySummary) so UPC is part of the API contract and form payloads.
Wired product import flow so the barcode returned from product lookup is placed into the item create payload and persisted (backend maps UPC into the existing import_ref storage field to avoid ent codegen changes in this rollout).
Extended repository search to include UPC (implemented via ImportRefContainsFold where UPC is stored) so searching by barcode will find matching items.
Added duplicate-detection UI in the Barcode/Product Import modal that queries existing items for the scanned UPC and shows quick “Open” buttons for matches before importing a new item.
Exposed UPC in the item create modal and item edit form and added an English locale key for the items.upc label.

Testing

Attempted to run backend repository tests with go test ./internal/data/repo -run TestNormalizeSearchQueryIntegration, but execution did not complete in this environment (test run/timeouts and go generate failed due to external network/module fetch restrictions), so the full automated test suite was not completed.
Verified repository changes were committed successfully (git commit completed) and performed local inspections of modified TypeScript and Vue files to ensure the new upc fields are passed through create/update flows and rendered in the UI.
Manual smoke checks in code: ensured product import modal now queries api.items.getAll and filters by upc to display existing items (no runtime test harness was executed here).
image image

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

Walkthrough

This PR adds UPC field support across the backend and frontend systems. The backend models (EntityCreate, EntityUpdate, EntitySummary, EntityOut) now include a upc field with mapping to the existing ImportRef column. The frontend updates corresponding API type contracts, adds UPC input to item creation and editing forms, and implements a barcode modal feature that checks for existing matching UPCs before searching products.

Changes

Cohort / File(s) Summary
Backend Entity Models
backend/internal/data/repo/repo_entities.go
Added UPC field to create, update, and response structs with validation. Implements ImportRef selection logic (prefers UPC when provided), expanded group query search to match ImportRef, and update/duplication logic for the field.
Frontend API Type Contracts
frontend/lib/api/types/data-contracts.ts
Generated TypeScript interfaces updated to include upc: string property across EntityCreate, EntityUpdate, EntityOut, and EntitySummary.
Frontend Item Components
frontend/components/Item/CreateModal.vue, frontend/pages/item/[id]/index/edit.vue
Added upc form field to item creation and editing workflows, with population from barcode data and form submission.
Frontend Barcode Modal
frontend/components/Item/BarcodeModal.vue
Queries for existing entities matching entered UPC before product search, displays matching items with navigation buttons, and manages existingItems state lifecycle.
Frontend Localization
frontend/locales/en.json
Added English translation entry for "upc" label.

Estimated Code Review Effort

🎯 2 (Simple) | ⏱️ ~13 minutes

Suggested Reviewers

  • tankerkiller125

Security Recommendations

  • Field Validation: The UPC field is properly constrained to max=64 characters in both backend and frontend, preventing potential overflow or storage issues.
  • Query Safety: The barcode modal's entity lookup query should be verified to use parameterized queries to prevent injection attacks (appears to be delegated to the API layer, which is appropriate).
  • ⚠️ ImportRef Column: Ensure that overwriting ImportRef with UPC values doesn't conflict with existing data migration strategies or third-party system integrations that may depend on the ImportRef field's original content.
  • 💡 Duplicate Handling: Verify that the UPC matching logic in BarcodeModal handles cases where multiple items could share the same UPC, and that the UI clearly indicates when duplicates exist.

Poem

🏷️ A barcode joins the dance,
UPC fields now prance,
From backend to frontend's light,
Searching items left and right,
Inventory takes its chance! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description provides comprehensive motivation, implementation details, and testing information, but is structured outside the required template format with missing explicit section headers. Reorganize the description to match the repository template: add 'What type of PR is this?' (feature), clarify 'What this PR does' with the bulleted file changes, and add 'Which issue(s) this PR fixes' with issue numbers.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: adding UPC support across product import, search, and the duplicate scan flow.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 requested a review from tankerkiller125 April 27, 2026 18:16
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
backend/internal/data/repo/repo_entities.go (1)

96-97: Harden UPC input handling on the backend.

UPC now comes from user input but only has max-length validation. Add canonicalization/allowlist validation before persisting to ImportRef to prevent storing malformed/control-character values.

🔐 Suggested hardening
+var upcRegex = regexp.MustCompile(`^[0-9]{8,14}$`)
...
- importRef := data.ImportRef
- if data.UPC != "" {
-   importRef = data.UPC
- }
+ importRef := data.ImportRef
+ normalizedUPC := strings.TrimSpace(data.UPC)
+ if normalizedUPC != "" {
+   if !upcRegex.MatchString(normalizedUPC) {
+     return EntityOut{}, fmt.Errorf("invalid upc format")
+   }
+   importRef = normalizedUPC
+ }

Also applies to: 120-121, 1003-1009

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

In `@backend/internal/data/repo/repo_entities.go` around lines 96 - 97, The UPC
field on ImportRef is only length-validated and may contain control or malformed
characters; before persisting, canonicalize and allowlist-validate it: trim
whitespace, normalize unicode (NFC), strip or reject control characters, and
enforce an allowlist regex (e.g. only digits and uppercase letters, and still
max length 64) then replace ImportRef.UPC with the canonical value and return a
validation error if it fails; apply the same canonicalization/validation
wherever ImportRef.UPC is set (the UPC struct field in repo_entities.go and the
code paths noted around the other occurrences) so persistence always uses the
cleaned, validated UPC.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/internal/data/repo/repo_entities.go`:
- Around line 1437-1441: The update query always calls SetImportRef(data.UPC)
which will clear the stored import reference when data.UPC is empty; modify the
update builder in the repository (the r.db.Entity.Update() chain that uses
entity.ID(...), entity.HasGroupWith(...), SetName, SetDescription,
SetSerialNumber) to only call SetImportRef(data.UPC) when data.UPC is present
(e.g. data.UPC != "" or non-nil depending on type); implement this by branching
before building the query (add the SetImportRef call conditionally) or by using
an optional/conditional builder pattern so existing ImportRef is preserved when
UPC is not supplied.

In `@frontend/components/Item/BarcodeModal.vue`:
- Around line 14-15: The UI text in BarcodeModal.vue is misleading because
existingItems (from getAll) is capped to 10, so update the message near the span
that uses existingItems.length and barcode to indicate it's a partial result
(e.g., "Showing first {{ existingItems.length }} matches for UPC {{ barcode }}")
or programmatically append " (showing first N results)" when getAll returns a
limited page; reference the existingItems variable and the getAll call that
fetches items to ensure the displayed copy reflects the page cap wherever
similar messages appear (also update the same pattern at the other occurrence
around lines 228-231).
- Around line 8-19: The two hardcoded UI strings in the template should be
converted to translatable messages: replace the "Found {{ existingItems.length
}} existing item(s) with UPC {{ barcode }}." span and the Button label "Open {{
existing.name }}" with i18n lookup keys (e.g. barcodeModal.foundExistingItems
and barcodeModal.openItem) and pass the dynamic values (existingItems.length,
barcode, existing.name) as interpolation params to the translation function;
update the template to call your project's i18n helper (e.g. $t or useI18n t())
for the span and the Button children and add the corresponding keys to the
locale files. Ensure you update nodes referencing existingItems, barcode,
existing.name and navigateToItem remains unchanged.

In `@frontend/components/Item/CreateModal.vue`:
- Around line 603-607: When a product is imported via barcode the UPC is
assigned to form.upc from params.product.barcode, but the create() branch that
calls api.templates.createItem(...) when templateData.value is present never
forwards form.upc so scanned UPCs are lost; update the create() logic to include
the UPC (use form.upc) in the payload passed to api.templates.createItem and any
other template-related creation calls (also check the same for the alternative
branch used at lines ~646-666), ensuring templateData.value creation uses the
same fields as the non-template path so the UPC is preserved.

---

Nitpick comments:
In `@backend/internal/data/repo/repo_entities.go`:
- Around line 96-97: The UPC field on ImportRef is only length-validated and may
contain control or malformed characters; before persisting, canonicalize and
allowlist-validate it: trim whitespace, normalize unicode (NFC), strip or reject
control characters, and enforce an allowlist regex (e.g. only digits and
uppercase letters, and still max length 64) then replace ImportRef.UPC with the
canonical value and return a validation error if it fails; apply the same
canonicalization/validation wherever ImportRef.UPC is set (the UPC struct field
in repo_entities.go and the code paths noted around the other occurrences) so
persistence always uses the cleaned, validated UPC.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8299dc64-e7d2-442a-a479-f43fa491bf98

📥 Commits

Reviewing files that changed from the base of the PR and between 0e0c171 and 6a37b33.

📒 Files selected for processing (6)
  • backend/internal/data/repo/repo_entities.go
  • frontend/components/Item/BarcodeModal.vue
  • frontend/components/Item/CreateModal.vue
  • frontend/lib/api/types/data-contracts.ts
  • frontend/locales/en.json
  • frontend/pages/item/[id]/index/edit.vue

Comment on lines 1437 to 1441
q := r.db.Entity.Update().Where(entity.ID(data.ID), entity.HasGroupWith(group.ID(gid))).
SetName(data.Name).
SetDescription(data.Description).
SetImportRef(data.UPC).
SetSerialNumber(data.SerialNumber).
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

Avoid unconditional ImportRef overwrite on update.

SetImportRef(data.UPC) always executes. If UPC is absent/empty from a caller, this clears existing data unexpectedly.

✅ Proposed fix
 q := r.db.Entity.Update().Where(entity.ID(data.ID), entity.HasGroupWith(group.ID(gid))).
   SetName(data.Name).
   SetDescription(data.Description).
-  SetImportRef(data.UPC).
   SetSerialNumber(data.SerialNumber).
   SetModelNumber(data.ModelNumber).
   SetManufacturer(data.Manufacturer).
   SetArchived(data.Archived).
@@
   SetQuantity(data.Quantity).
   SetAssetID(int64(data.AssetID)).
   SetSyncChildEntityLocations(data.SyncChildEntityLocations)
+
+if strings.TrimSpace(data.UPC) != "" {
+  q.SetImportRef(strings.TrimSpace(data.UPC))
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/internal/data/repo/repo_entities.go` around lines 1437 - 1441, The
update query always calls SetImportRef(data.UPC) which will clear the stored
import reference when data.UPC is empty; modify the update builder in the
repository (the r.db.Entity.Update() chain that uses entity.ID(...),
entity.HasGroupWith(...), SetName, SetDescription, SetSerialNumber) to only call
SetImportRef(data.UPC) when data.UPC is present (e.g. data.UPC != "" or non-nil
depending on type); implement this by branching before building the query (add
the SetImportRef call conditionally) or by using an optional/conditional builder
pattern so existing ImportRef is preserved when UPC is not supplied.

Comment on lines +8 to +19
<div
v-if="existingItems.length > 0"
class="flex flex-col gap-2 rounded-md border border-amber-500/40 bg-amber-500/10 p-4 text-amber-700 dark:text-amber-300"
role="alert"
>
<span class="text-sm font-medium">
Found {{ existingItems.length }} existing item(s) with UPC {{ barcode }}.
</span>
<div class="flex flex-wrap gap-2">
<Button v-for="existing in existingItems" :key="existing.id" size="sm" variant="outline" @click="navigateToItem(existing.id)">
Open {{ existing.name }}
</Button>
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 | 🟡 Minor

Localize the new duplicate-alert copy.

The strings Found ... existing item(s)... and Open ... are hardcoded and won’t be translated.

🌐 Proposed fix
-        <span class="text-sm font-medium">
-          Found {{ existingItems.length }} existing item(s) with UPC {{ barcode }}.
-        </span>
+        <span class="text-sm font-medium">
+          {{ $t("components.item.product_import.duplicates_found", { count: existingItems.length, barcode }) }}
+        </span>
         <div class="flex flex-wrap gap-2">
           <Button v-for="existing in existingItems" :key="existing.id" size="sm" variant="outline" `@click`="navigateToItem(existing.id)">
-            Open {{ existing.name }}
+            {{ $t("components.item.product_import.open_item", { name: existing.name }) }}
           </Button>
         </div>

As per coding guidelines: "**/*.{ts,vue}: Check for hardcoded strings in UI components that should be translatable."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div
v-if="existingItems.length > 0"
class="flex flex-col gap-2 rounded-md border border-amber-500/40 bg-amber-500/10 p-4 text-amber-700 dark:text-amber-300"
role="alert"
>
<span class="text-sm font-medium">
Found {{ existingItems.length }} existing item(s) with UPC {{ barcode }}.
</span>
<div class="flex flex-wrap gap-2">
<Button v-for="existing in existingItems" :key="existing.id" size="sm" variant="outline" @click="navigateToItem(existing.id)">
Open {{ existing.name }}
</Button>
<div
v-if="existingItems.length > 0"
class="flex flex-col gap-2 rounded-md border border-amber-500/40 bg-amber-500/10 p-4 text-amber-700 dark:text-amber-300"
role="alert"
>
<span class="text-sm font-medium">
{{ $t("components.item.product_import.duplicates_found", { count: existingItems.length, barcode }) }}
</span>
<div class="flex flex-wrap gap-2">
<Button v-for="existing in existingItems" :key="existing.id" size="sm" variant="outline" `@click`="navigateToItem(existing.id)">
{{ $t("components.item.product_import.open_item", { name: existing.name }) }}
</Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/components/Item/BarcodeModal.vue` around lines 8 - 19, The two
hardcoded UI strings in the template should be converted to translatable
messages: replace the "Found {{ existingItems.length }} existing item(s) with
UPC {{ barcode }}." span and the Button label "Open {{ existing.name }}" with
i18n lookup keys (e.g. barcodeModal.foundExistingItems and
barcodeModal.openItem) and pass the dynamic values (existingItems.length,
barcode, existing.name) as interpolation params to the translation function;
update the template to call your project's i18n helper (e.g. $t or useI18n t())
for the span and the Button children and add the corresponding keys to the
locale files. Ensure you update nodes referencing existingItems, barcode,
existing.name and navigateToItem remains unchanged.

Comment on lines +14 to +15
Found {{ existingItems.length }} existing item(s) with UPC {{ barcode }}.
</span>
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 | 🟡 Minor

Duplicate count may be underreported due fixed page size.

The alert says “Found N existing item(s)”, but getAll is capped at 10 results. If there are more matches, the message is inaccurate.

🛠️ Proposed fix
-      const existingResult = await api.items.getAll({ q: barcode.trim(), page: 1, pageSize: 10 });
+      const existingResult = await api.items.getAll({ q: barcode.trim(), page: 1, pageSize: 100 });

If you keep paging capped, adjust the text to indicate it is a partial list (e.g., “showing first N matches”).

Also applies to: 228-231

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

In `@frontend/components/Item/BarcodeModal.vue` around lines 14 - 15, The UI text
in BarcodeModal.vue is misleading because existingItems (from getAll) is capped
to 10, so update the message near the span that uses existingItems.length and
barcode to indicate it's a partial result (e.g., "Showing first {{
existingItems.length }} matches for UPC {{ barcode }}") or programmatically
append " (showing first N results)" when getAll returns a limited page;
reference the existingItems variable and the getAll call that fetches items to
ensure the displayed copy reflects the page cap wherever similar messages appear
(also update the same pattern at the other occurrence around lines 228-231).

Comment on lines 603 to 607
if (params?.product) {
form.name = params.product.item.name;
form.description = params.product.item.description;
form.upc = params.product.barcode;

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

UPC is dropped when creation goes through the template endpoint.

form.upc is set from barcode import, but when templateData.value is present, create() uses api.templates.createItem(...) without UPC. This can silently lose scanned UPC values.

💡 Proposed fix (preserve UPC immediately)
-    if (templateData.value) {
+    if (templateData.value && !form.upc) {
       const templateRequest = {
         name: form.name,
         description: form.description,
         parentId: form.location.id as string,
         tagIds: form.tags,
         quantity: form.quantity,
       };

       const result = await api.templates.createItem(templateData.value.id, templateRequest);
       error = result.error;
       data = result.data;
     } else {
       // Normal item creation without template
       const out: EntityCreate = {
         parentId: form.parentId || form.location.id as string,
         name: form.name,
         quantity: form.quantity,
         upc: form.upc,
         description: form.description,
         tagIds: form.tags,
         entityTypeId: selectedEntityType.value?.id,
       };

Also applies to: 646-666

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

In `@frontend/components/Item/CreateModal.vue` around lines 603 - 607, When a
product is imported via barcode the UPC is assigned to form.upc from
params.product.barcode, but the create() branch that calls
api.templates.createItem(...) when templateData.value is present never forwards
form.upc so scanned UPCs are lost; update the create() logic to include the UPC
(use form.upc) in the payload passed to api.templates.createItem and any other
template-related creation calls (also check the same for the alternative branch
used at lines ~646-666), ensuring templateData.value creation uses the same
fields as the non-template path so the UPC is preserved.

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.

1 participant