Add UPC validation and automatic item number generation/validation

This commit is contained in:
2025-03-01 19:37:51 -05:00
parent 8271c9f95a
commit 98e3b89d46
5 changed files with 1225 additions and 143 deletions

View File

@@ -269,10 +269,6 @@ export function ProductSearchDialog({ isOpen, onClose, onTemplateCreated }: Prod
};
const handleProductSelect = (product: Product) => {
console.log('Selected product supplier data:', {
vendor: product.vendor,
vendor_reference: product.vendor_reference
});
// Ensure all values are of the correct type
setFormData({
@@ -303,17 +299,13 @@ export function ProductSearchDialog({ isOpen, onClose, onTemplateCreated }: Prod
// Try to find the supplier ID from the vendor name
if (product.vendor && fieldOptions) {
console.log('Available suppliers:', fieldOptions.suppliers);
console.log('Looking for supplier match for vendor:', product.vendor);
// First try exact match
let supplierOption = fieldOptions.suppliers.find(
let supplierOption = fieldOptions.suppliers.find(
supplier => supplier.label.toLowerCase() === product.vendor.toLowerCase()
);
// If no exact match, try partial match
if (!supplierOption) {
console.log('No exact match found, trying partial match');
supplierOption = fieldOptions.suppliers.find(
supplier => supplier.label.toLowerCase().includes(product.vendor.toLowerCase()) ||
product.vendor.toLowerCase().includes(supplier.label.toLowerCase())
@@ -325,11 +317,7 @@ export function ProductSearchDialog({ isOpen, onClose, onTemplateCreated }: Prod
...prev,
supplier: supplierOption.value
}));
console.log('Found supplier match:', {
vendorName: product.vendor,
matchedSupplier: supplierOption.label,
supplierId: supplierOption.value
});
} else {
console.log('No supplier match found for vendor:', product.vendor);
}
@@ -337,7 +325,7 @@ export function ProductSearchDialog({ isOpen, onClose, onTemplateCreated }: Prod
// Fetch product categories
if (product.pid) {
console.log('Fetching categories for product ID:', product.pid);
fetchProductCategories(product.pid);
}
@@ -348,7 +336,7 @@ export function ProductSearchDialog({ isOpen, onClose, onTemplateCreated }: Prod
const fetchProductCategories = async (productId: number) => {
try {
const response = await axios.get(`/api/import/product-categories/${productId}`);
console.log('Product categories:', response.data);
if (response.data && Array.isArray(response.data)) {
// Filter out categories with type 20 (themes) and type 21 (subthemes)
@@ -356,7 +344,6 @@ export function ProductSearchDialog({ isOpen, onClose, onTemplateCreated }: Prod
category.type !== 20 && category.type !== 21
);
console.log('Filtered categories (excluding themes):', filteredCategories);
// Extract category IDs and update form data
const categoryIds = filteredCategories.map((category: any) => category.value);
@@ -422,28 +409,14 @@ export function ProductSearchDialog({ isOpen, onClose, onTemplateCreated }: Prod
// Log supplier information for debugging
if (formData.supplier) {
const supplierOption = fieldOptions?.suppliers.find(
supplier => supplier.value === formData.supplier
);
console.log('Submitting supplier:', {
id: formData.supplier,
name: supplierOption?.label || 'Unknown',
allSuppliers: fieldOptions?.suppliers.map(s => ({ id: s.value, name: s.label }))
});
} else {
console.log('No supplier selected for submission');
}
// Log categories information for debugging
if (formData.categories && formData.categories.length > 0) {
const categoryOptions = formData.categories.map(catId => {
const category = fieldOptions?.categories.find(c => c.value === catId);
return {
id: catId,
name: category?.label || 'Unknown Category'
};
});
console.log('Submitting categories:', categoryOptions);
} else {
console.log('No categories selected for submission');
}
@@ -471,11 +444,9 @@ export function ProductSearchDialog({ isOpen, onClose, onTemplateCreated }: Prod
ship_restrictions: formData.ship_restrictions || null
};
console.log('Sending template data:', dataToSend);
const response = await axios.post('/api/templates', dataToSend);
console.log('Template creation response:', response);
if (response.status >= 200 && response.status < 300) {
toast.success('Template created successfully');
@@ -1040,9 +1011,7 @@ export function ProductSearchDialog({ isOpen, onClose, onTemplateCreated }: Prod
const filteredCategories = fieldOptions.categories.filter(
category => category.type && validCategoryTypes.includes(category.type)
);
console.log('Filtered categories for dropdown:', filteredCategories.length);
return [...filteredCategories].sort((a, b) => {
const aSelected = selected.has(a.value);
const bSelected = selected.has(b.value);
@@ -1171,10 +1140,7 @@ export function ProductSearchDialog({ isOpen, onClose, onTemplateCreated }: Prod
onSelect={() => {
// Make sure we're setting the ID (value), not the label
handleSelectChange('supplier', supplier.value);
console.log('Selected supplier from dropdown:', {
label: supplier.label,
value: supplier.value
});
}}
>
<Check

View File

@@ -65,7 +65,7 @@ export type Data<T extends string> = {
// Data model RSI uses for spreadsheet imports
export type Fields<T extends string> = DeepReadonly<Field<T>[]>
export type Field<T extends string = string> = {
export type Field<T extends string> = {
// UI-facing field label
label: string
// Field's unique identifier
@@ -75,14 +75,14 @@ export type Field<T extends string = string> = {
// Alternate labels used for fields' auto-matching, e.g. "fname" -> "firstName"
alternateMatches?: string[]
// Validations used for field entries
validations?: Validation[]
validations?: ValidationConfig[]
// Field entry component
fieldType: FieldType
// UI-facing values shown to user as field examples pre-upload phase
example?: string
width?: number
disabled?: boolean
onChange?: (value: string) => void
onChange?: (value: any, additionalData?: any) => void
}
export type FieldType =

View File

@@ -23,6 +23,39 @@ const BASE_IMPORT_FIELDS = [
width: 220,
validations: [{ rule: "required" as const, errorMessage: "Required", level: "error" as ErrorLevel }],
},
{
label: "Company",
key: "company",
description: "Company/Brand name",
fieldType: {
type: "select",
options: [], // Will be populated from API
},
width: 200,
validations: [{ rule: "required", errorMessage: "Required", level: "error" }],
},
{
label: "Line",
key: "line",
description: "Product line",
alternateMatches: ["collection"],
fieldType: {
type: "select",
options: [], // Will be populated dynamically based on company selection
},
width: 180,
validations: [{ rule: "required", errorMessage: "Required", level: "error" }],
},
{
label: "Sub Line",
key: "subline",
description: "Product sub-line",
fieldType: {
type: "select",
options: [], // Will be populated dynamically based on line selection
},
width: 180,
},
{
label: "UPC",
key: "upc",
@@ -78,7 +111,7 @@ const BASE_IMPORT_FIELDS = [
key: "item_number",
description: "Internal item reference number",
fieldType: { type: "input" },
width: 120,
width: 130,
validations: [
{ rule: "required", errorMessage: "Required", level: "error" },
{ rule: "unique", errorMessage: "Must be unique", level: "error" },
@@ -148,39 +181,6 @@ const BASE_IMPORT_FIELDS = [
width: 180,
validations: [{ rule: "required", errorMessage: "Required", level: "error" }],
},
{
label: "Company",
key: "company",
description: "Company/Brand name",
fieldType: {
type: "select",
options: [], // Will be populated from API
},
width: 200,
validations: [{ rule: "required", errorMessage: "Required", level: "error" }],
},
{
label: "Line",
key: "line",
description: "Product line",
alternateMatches: ["collection"],
fieldType: {
type: "select",
options: [], // Will be populated dynamically based on company selection
},
width: 180,
validations: [{ rule: "required", errorMessage: "Required", level: "error" }],
},
{
label: "Sub Line",
key: "subline",
description: "Product sub-line",
fieldType: {
type: "select",
options: [], // Will be populated dynamically based on line selection
},
width: 180,
},
{
label: "Artist",
key: "artist",