Add UPC support to product import, search, and duplicate scan flow#1468
Add UPC support to product import, search, and duplicate scan flow#1468mch987 wants to merge 1 commit into
Conversation
WalkthroughThis PR adds UPC field support across the backend and frontend systems. The backend models ( Changes
Estimated Code Review Effort🎯 2 (Simple) | ⏱️ ~13 minutes Suggested Reviewers
Security Recommendations
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
ImportRefto 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
📒 Files selected for processing (6)
backend/internal/data/repo/repo_entities.gofrontend/components/Item/BarcodeModal.vuefrontend/components/Item/CreateModal.vuefrontend/lib/api/types/data-contracts.tsfrontend/locales/en.jsonfrontend/pages/item/[id]/index/edit.vue
| 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). |
There was a problem hiding this comment.
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.
| <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> |
There was a problem hiding this comment.
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.
| <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.
| Found {{ existingItems.length }} existing item(s) with UPC {{ barcode }}. | ||
| </span> |
There was a problem hiding this comment.
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).
| if (params?.product) { | ||
| form.name = params.product.item.name; | ||
| form.description = params.product.item.description; | ||
| form.upc = params.product.barcode; | ||
|
|
There was a problem hiding this comment.
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.
Motivation
Description
Testing