import { defineSisHandler } from "../../../../utils/listings/routeHelper" import { findListingByKey, findListingBySku, insertListing, queueChange } from "../../../../utils/listings/listingStore" import { getListingAdapter, supportsListings } from "../../../../utils/listings/adapters" import { resolveOrderSourceCurrencyId } from "../../../../utils/listings/importer" import { flushNowIfImmediate } from "../../../../utils/listings/immediate" // POST /api/sales/sms/listings/create // { orderSourceId, items: [{ productId, asin?, price, qty, minPrice?, maxPrice? }] } // "List products": creates a pending listing per product and queues a `create` // change. Amazon: offer-only PUT against the ASIN (ListingKey = SKU). Shopify: a new // product + variant is created by the adapter, which then replaces the provisional // ListingKey (= SKU) with the variant GID and fills product / inventory ids. export default defineSisHandler(async ({ event, f, userLabel }) => { const body = await readBody(event).catch(() => ({})) const orderSourceId = Number(body?.orderSourceId) const items: any[] = Array.isArray(body?.items) ? body.items : [] if (!orderSourceId) return { status: 400, message: 'orderSourceId is required' } if (!items.length) return { status: 400, message: 'items[] is empty' } if (items.length > 200) return { status: 400, message: 'at most 200 items per call' } const orderSource: any = await f(`models/c_ordersource/${orderSourceId}`) if (!orderSource?.id) return { status: 404, message: 'Order source not found' } const adapter = getListingAdapter(orderSource) if (!adapter || !supportsListings(orderSource)) return { status: 400, message: 'This order source has no listings adapter' } const orgId = Number(orderSource?.AD_Org_ID?.id) || 0 const cur = await resolveOrderSourceCurrencyId(f, orderSource, 'EUR') const ctx: any = { orderSource, orderSourceId, orgId, marketplace: adapter.key, fetch: f, log: () => {} } const created: any[] = []; const errors: { productId: number; error: string }[] = [] for (const it of items) { const productId = Number(it?.productId) try { if (!productId) throw new Error('productId missing') const product: any = await f(`models/m_product/${productId}`) if (!product?.id) throw new Error('product not found') const value = String(product.Value || '').trim() const isAmazon = adapter.key === 'amazon' let asin = isAmazon ? (String(it?.asin || product.asin || '').trim().toUpperCase() || null) : null const price = Number(String(it?.price ?? '').replace(',', '.')) const qty = it?.qty != null && it?.qty !== '' ? Math.trunc(Number(it.qty)) : 0 if (!Number.isFinite(price) || price <= 0) throw new Error('price must be a positive number') if (!Number.isFinite(qty) || qty < 0) throw new Error('qty must be >= 0') const why = adapter.validateCreate?.(ctx, { value, asin }) if (why) throw new Error(why) if (await findListingByKey(f, orderSourceId, value)) throw new Error(`already listed (key ${value})`) if (!isAmazon && await findListingBySku(f, orderSourceId, value)) throw new Error(`already listed (SKU ${value})`) if (asin && !product.asin) await f(`models/m_product/${productId}`, 'PUT', { asin }) const minPrice = it?.minPrice != null && it?.minPrice !== '' ? Number(String(it.minPrice).replace(',', '.')) : null const maxPrice = it?.maxPrice != null && it?.maxPrice !== '' ? Number(String(it.maxPrice).replace(',', '.')) : null const listing = await insertListing(f, { orgId, orderSourceId, sellerSku: value, listingKey: value, name: product.Name || value, productId, externalProductId: asin, listingStatus: 'pending', fulfillmentChannel: 'DEFAULT', price: Math.round(price * 100) / 100, qty, minPrice, maxPrice, currencyId: cur.id, isPriceTaxIncluded: true, syncStatus: 'queued', meta: { createdFrom: 'erp', by: userLabel, marketplace: adapter.key } }) const change = await queueChange(f, listing, { changeType: 'create', newValue: String(price), description: userLabel }) created.push({ listingId: listing.id, productId, sku: value, asin, changeId: change.id }) } catch (e: any) { errors.push({ productId, error: String(e?.data?.detail || e?.message || e).slice(0, 300) }) } } const delivered = created.length ? await flushNowIfImmediate(f, orderSource) : null console.log('[sis] list products', { user: userLabel, orderSourceId, created: created.length, errors: errors.length, immediate: !!delivered }) return { status: 200, created, errors, delivered: delivered ? { sent: delivered.sources?.[0]?.sent || 0, errors: delivered.sources?.[0]?.errors || 0 } : null } })