fix: improve product search robustness

- search-sheets: ne bloque plus sur l'absence d'ID Shopify en col A
- Shopify GraphQL: first:10 + match exact insensible à la casse au lieu de first:1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 17:54:56 +02:00
parent 920ec50402
commit 36447a5c8c
2 changed files with 27 additions and 19 deletions
+3 -2
View File
@@ -50,9 +50,10 @@ export async function GET(req: NextRequest) {
const refLower = ref.toLowerCase() const refLower = ref.toLowerCase()
const ugsLower = ugs.toLowerCase() const ugsLower = ugs.toLowerCase()
const wordBoundary = new RegExp(`(^|[^a-z0-9])${ugsLower.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([^a-z0-9]|$)`) const wordBoundary = new RegExp(`(^|[^a-z0-9])${ugsLower.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([^a-z0-9]|$)`)
const isMatch = ugs && shopifyId && wordBoundary.test(refLower) // Ne requiert pas shopifyId pour le match UGS — ID peut être absent si produit externe
const isMatch = ugs && wordBoundary.test(refLower)
if (isMatch) { if (isMatch && shopifyId) {
const title = (row[3] ?? '').trim() const title = (row[3] ?? '').trim()
if (!bestMatch || ugs.length > bestMatch.ugsLength) { if (!bestMatch || ugs.length > bestMatch.ugsLength) {
bestMatch = { shopifyId, title, ugsLength: ugs.length } bestMatch = { shopifyId, title, ugsLength: ugs.length }
+24 -17
View File
@@ -27,29 +27,36 @@ export type ShopifyProductResult =
export async function searchProductByRef(ref: string): Promise<ShopifyProductResult> { export async function searchProductByRef(ref: string): Promise<ShopifyProductResult> {
const { baseUrl, headers } = getConfig() const { baseUrl, headers } = getConfig()
// Recherche par SKU via l'API variants // Recherche par SKU via l'API GraphQL (supporte les SKUs avec tirets, contrairement au REST)
const graphqlUrl = `https://${process.env.SHOPIFY_STORE_DOMAIN}/admin/api/${API_VERSION}/graphql.json`
const searchBySku = async (sku: string): Promise<ShopifyProductResult> => { const searchBySku = async (sku: string): Promise<ShopifyProductResult> => {
const res = await fetch( const query = `{
`${baseUrl}/variants.json?sku=${encodeURIComponent(sku)}&fields=id,sku,product_id&limit=1`, productVariants(first: 10, query: "sku:${sku.replace(/"/g, '')}") {
{ headers }, edges {
) node {
sku
product { id legacyResourceId title }
}
}
}
}`
const res = await fetch(graphqlUrl, {
method: 'POST',
headers,
body: JSON.stringify({ query }),
})
if (!res.ok) return { found: false } if (!res.ok) return { found: false }
const data = await res.json() const data = await res.json()
const variants = (data.variants ?? []) as Array<{ id: number; sku: string; product_id: number }> const edges = data?.data?.productVariants?.edges ?? []
const match = variants.find(v => v.sku === sku) // Cherche le match exact (insensible à la casse) parmi les résultats
if (!match) return { found: false } const match = edges.find((e: { node: { sku: string } }) =>
e.node.sku.toLowerCase() === sku.toLowerCase()
// Récupère le titre du produit
const productRes = await fetch(
`${baseUrl}/products/${match.product_id}.json?fields=id,title`,
{ headers },
) )
if (!productRes.ok) return { found: false } if (!match) return { found: false }
const productData = await productRes.json()
return { return {
found: true, found: true,
shopifyId: String(match.product_id), shopifyId: match.node.product.legacyResourceId,
title: productData.product?.title ?? sku, title: match.node.product.title,
} }
} }