Files
gestion-materiaux-destock/src/app/api/creation/sync-stock/route.ts
T
2026-06-26 11:40:12 +02:00

141 lines
5.9 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { findColIndex } from '@/lib/creationSheets'
import { getConfig } from '@/lib/shopifySync'
export async function POST(req: NextRequest) {
const session = await getServerSession(authOptions)
if (!session) return NextResponse.json({ error: 'Non authentifié' }, { status: 401 })
const { tab } = await req.json().catch(() => ({})) as { tab?: string }
const stream = new ReadableStream({
async start(controller) {
const send = (obj: object) => controller.enqueue(new TextEncoder().encode(JSON.stringify(obj) + '\n'))
try {
const { baseUrl, headers } = getConfig()
// Récupérer le location_id une seule fois
const locRes = await fetch(`${baseUrl}/locations.json`, { headers })
if (!locRes.ok) throw new Error(`Shopify locations error: ${locRes.status}`)
const locData = await locRes.json() as { locations: Array<{ id: number }> }
const locationId = locData.locations[0]?.id
if (!locationId) throw new Error('Aucune location Shopify trouvée')
// Lire tous les onglets avec un ID Shopify en colonne A
const apiKey = process.env.GOOGLE_API_KEY
const sheetId = process.env.GOOGLE_SHEETS_ID
if (!apiKey || !sheetId) throw new Error('GOOGLE_API_KEY et GOOGLE_SHEETS_ID requis')
let tabs: string[]
if (tab) {
tabs = [tab]
} else {
const metaRes = await fetch(
`https://sheets.googleapis.com/v4/spreadsheets/${sheetId}?key=${apiKey}&fields=sheets.properties.title`,
)
if (!metaRes.ok) throw new Error(`Sheets metadata error: ${metaRes.status}`)
const meta = await metaRes.json()
tabs = (meta.sheets as Array<{ properties: { title: string } }>).map(s => s.properties.title)
}
type StockEntry = { shopifyId: string; stock: number; title: string; tab: string }
const entries: StockEntry[] = []
for (const t of tabs) {
const range = encodeURIComponent(`${t}!A1:BZ`)
const res = await fetch(`https://sheets.googleapis.com/v4/spreadsheets/${sheetId}/values/${range}?key=${apiKey}`)
if (!res.ok) continue
const data = await res.json()
const rows = (data.values ?? []) as string[][]
if (rows.length < 2) continue
const headerRowIdx = rows.findIndex(r => (r[0] ?? '').trim() === 'ID')
if (headerRowIdx === -1) continue
const hdrs = rows[headerRowIdx]
if (findColIndex(hdrs, 'Publié') === -1) continue
const idIdx = 0
const nomIdx = findColIndex(hdrs, 'Nom')
const stockIdx = findColIndex(hdrs, 'Stock (en pièce)') !== -1
? findColIndex(hdrs, 'Stock (en pièce)')
: findColIndex(hdrs, 'Stock (pièce)')
const stockCdtIdx = findColIndex(hdrs, 'Stock conditionné')
const get = (row: string[], i: number) => (i >= 0 ? (row[i] ?? '').trim() : '')
for (const row of rows.slice(headerRowIdx + 1)) {
const shopifyId = get(row, idIdx)
if (!shopifyId || !/^\d+$/.test(shopifyId)) continue
const rawStock = get(row, stockCdtIdx) || get(row, stockIdx)
const stock = parseInt(rawStock || '0', 10) || 0
entries.push({ shopifyId, stock, title: get(row, nomIdx), tab: t })
}
}
send({ type: 'total', total: entries.length })
let updated = 0
let errors = 0
for (let i = 0; i < entries.length; i++) {
const { shopifyId, stock, title } = entries[i]
send({ type: 'progress', current: i + 1, total: entries.length, title })
try {
// Récupérer le variant pour obtenir inventory_item_id
const pRes = await fetch(`${baseUrl}/products/${shopifyId}.json?fields=variants`, { headers })
if (pRes.status === 404) {
// Produit supprimé de Shopify, on ignore silencieusement
continue
}
if (!pRes.ok) {
const msg = `Produit ${shopifyId} erreur (${pRes.status})`
send({ type: 'error', title, message: msg })
errors++
continue
}
const pData = await pRes.json() as { product: { variants: Array<{ inventory_item_id: number }> } }
const inventoryItemId = pData.product.variants[0]?.inventory_item_id
if (!inventoryItemId) { errors++; continue }
// Connect (idempotent)
await fetch(`${baseUrl}/inventory_levels/connect.json`, {
method: 'POST', headers,
body: JSON.stringify({ location_id: locationId, inventory_item_id: inventoryItemId }),
})
const setRes = await fetch(`${baseUrl}/inventory_levels/set.json`, {
method: 'POST', headers,
body: JSON.stringify({ location_id: locationId, inventory_item_id: inventoryItemId, available: stock }),
})
if (!setRes.ok) {
const txt = await setRes.text()
send({ type: 'error', title, message: `stock set failed: ${setRes.status}${txt}` })
errors++
} else {
updated++
}
} catch (err) {
send({ type: 'error', title, message: err instanceof Error ? err.message : 'Erreur inconnue' })
errors++
}
}
send({ type: 'done', updated, errors })
} catch (err) {
const message = err instanceof Error ? err.message : 'Erreur inconnue'
console.error('[sync-stock] fatal:', message)
send({ type: 'done', updated: 0, errors: 1, fatalError: message })
} finally {
controller.close()
}
},
})
return new Response(stream, {
headers: { 'Content-Type': 'application/x-ndjson', 'Transfer-Encoding': 'chunked' },
})
}