npm package: @kodeme-io/next-core-product
Description: Product catalog module for Next.js + Odoo applications
Author: ABC Food
Latest version: 0.3.2

# @next-odoo/product

Product catalog module for Next.js + Odoo applications with barcode search, stock tracking, and inventory management.

## Features

- ✅ **Product Management** - CRUD operations for products
- ✅ **Barcode Search** - Find products by barcode
- ✅ **Stock Tracking** - Real-time inventory levels
- ✅ **Category Support** - Product categorization
- ✅ **UOM Management** - Multiple units of measure
- ✅ **Variant Support** - Product variants/templates
- ✅ **React Query Hooks** - Caching and optimistic updates
- ✅ **Type Safety** - Full TypeScript support

## Installation

```bash
pnpm add @next-odoo/product @next-odoo/odoo-api @tanstack/react-query
```

## Usage

### Setup

```typescript
import { createOdooClient } from '@next-odoo/odoo-api'
import { createProductAPI } from '@next-odoo/product'

const odoo = createOdooClient({
  url: process.env.NEXT_PUBLIC_ODOO_URL!,
  database: process.env.NEXT_PUBLIC_ODOO_DB!,
  sessionId: userSessionId
})

const productAPI = createProductAPI(odoo)
```

### Query Hooks

#### Get All Products

```typescript
import { useProducts } from '@next-odoo/product'

export function ProductList() {
  const { data: products, isLoading } = useProducts(productAPI, {
    domain: [['sale_ok', '=', true]],
    limit: 100,
    order: 'name ASC'
  })

  if (isLoading) return <div>Loading...</div>

  return (
    <ul>
      {products?.map(product => (
        <li key={product.id}>
          {product.name} - ${product.list_price}
        </li>
      ))}
    </ul>
  )
}
```

#### Get Single Product

```typescript
import { useProduct } from '@next-odoo/product'

export function ProductDetail({ id }: { id: number }) {
  const { data: product } = useProduct(productAPI, id)

  if (!product) return null

  return (
    <div>
      <h1>{product.name}</h1>
      <p>SKU: {product.default_code}</p>
      <p>Price: ${product.list_price}</p>
      <p>Stock: {product.qty_available} {product.uom_name}</p>
    </div>
  )
}
```

#### Search by Barcode

```typescript
import { useProductByBarcode } from '@next-odoo/product'
import { useState } from 'react'

export function BarcodeScanner() {
  const [barcode, setBarcode] = useState('')
  const { data: product, isLoading } = useProductByBarcode(productAPI, barcode)

  return (
    <div>
      <input
        value={barcode}
        onChange={(e) => setBarcode(e.target.value)}
        placeholder="Scan barcode..."
      />
      {isLoading && <div>Searching...</div>}
      {product && (
        <div>
          Found: {product.name} - ${product.list_price}
        </div>
      )}
    </div>
  )
}
```

#### Search Products

```typescript
import { useSearchProducts } from '@next-odoo/product'

export function ProductSearch() {
  const [query, setQuery] = useState('')
  const { data: results } = useSearchProducts(productAPI, query, 20)

  return (
    <div>
      <input
        type="search"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search products..."
      />
      {results?.map(product => (
        <div key={product.id}>{product.name}</div>
      ))}
    </div>
  )
}
```

#### Get Products by Category

```typescript
import { useProductsByCategory } from '@next-odoo/product'

export function CategoryProducts({ categoryId }: { categoryId: number }) {
  const { data: products } = useProductsByCategory(productAPI, categoryId)

  return (
    <div>
      <h2>Products in Category ({products?.length})</h2>
      {/* Render products */}
    </div>
  )
}
```

#### Get Product Categories

```typescript
import { useProductCategories } from '@next-odoo/product'

export function CategoryTree() {
  const { data: categories } = useProductCategories(productAPI)

  return (
    <ul>
      {categories?.map(cat => (
        <li key={cat.id}>{cat.complete_name}</li>
      ))}
    </ul>
  )
}
```

#### Check Stock Levels

```typescript
import { useProductStock } from '@next-odoo/product'

export function StockIndicator({ productId }: { productId: number }) {
  const { data: stock } = useProductStock(productAPI, productId)

  if (!stock) return null

  return (
    <div>
      <p>Available: {stock.qty_available}</p>
      <p>Forecasted: {stock.virtual_available}</p>
      <p>Incoming: {stock.incoming_qty}</p>
      <p>Outgoing: {stock.outgoing_qty}</p>
    </div>
  )
}
```

#### Check Availability

```typescript
import { useCheckAvailability } from '@next-odoo/product'

export function AvailabilityChecker({ productId, quantity }: Props) {
  const { data } = useCheckAvailability(productAPI, productId, quantity)

  return (
    <div>
      {data?.available ? (
        <span className="text-green-600">✓ In Stock</span>
      ) : (
        <span className="text-red-600">
          ✗ Only {data?.qty_available} available
        </span>
      )}
    </div>
  )
}
```

### Mutation Hooks

#### Create Product

```typescript
import { useCreateProduct } from '@next-odoo/product'

export function CreateProductForm() {
  const createProduct = useCreateProduct(productAPI)

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()

    try {
      const productId = await createProduct.mutateAsync({
        name: 'New Product',
        list_price: 99.99,
        categ_id: 1,
        uom_id: 1,
        active: true
      })

      console.log('Created product:', productId)
    } catch (error) {
      console.error('Failed to create product:', error)
    }
  }

  return <form onSubmit={handleSubmit}>{/* Form fields */}</form>
}
```

#### Update Product

```typescript
import { useUpdateProduct } from '@next-odoo/product'

export function EditProduct({ productId }: { productId: number }) {
  const updateProduct = useUpdateProduct(productAPI)

  const handleUpdate = async () => {
    await updateProduct.mutateAsync({
      id: productId,
      updates: {
        list_price: 149.99,
        description: 'Updated description'
      }
    })
  }

  return <button onClick={handleUpdate}>Update Price</button>
}
```

### Advanced Features

#### Product Variants

```typescript
import { useProductTemplates, useProductVariants } from '@next-odoo/product'

export function ProductVariantSelector({ templateId }: { templateId: number }) {
  const { data: variants } = useProductVariants(productAPI, templateId)

  return (
    <select>
      {variants?.map(variant => (
        <option key={variant.id} value={variant.id}>
          {variant.name} - ${variant.list_price}
        </option>
      ))}
    </select>
  )
}
```

#### Units of Measure

```typescript
import { useUnitOfMeasures } from '@next-odoo/product'

export function UOMSelector() {
  const { data: uoms } = useUnitOfMeasures(productAPI)

  return (
    <select>
      {uoms?.map(uom => (
        <option key={uom.id} value={uom.id}>
          {uom.name}
        </option>
      ))}
    </select>
  )
}
```

## API Reference

### ProductAPI

Direct API methods (used by hooks):

```typescript
const productAPI = createProductAPI(odooClient)

// Query methods
await productAPI.getProducts(options)
await productAPI.getProductById(id)
await productAPI.getProductByBarcode(barcode)
await productAPI.searchProducts(query, limit)
await productAPI.getProductsByCategory(categoryId)
await productAPI.getCategories()
await productAPI.getUnitOfMeasures()
await productAPI.getProductStock(productId, locationId)
await productAPI.checkAvailability(productId, quantity)

// Mutation methods
await productAPI.createProduct(data)
await productAPI.updateProduct(id, updates)
await productAPI.deleteProduct(id)  // Archives product

// Template/Variant methods
await productAPI.getProductTemplates(options)
await productAPI.getProductVariants(templateId)
```

## Types

### Product

```typescript
interface Product {
  id: number
  name: string
  default_code?: string  // SKU
  barcode?: string
  uom_id: number
  uom_name: string
  secondary_uom_id?: number
  secondary_uom_name?: string
  list_price: number  // Sale price
  standard_price: number  // Cost price
  categ_id: number
  categ_name: string
  active: boolean
  type?: 'consu' | 'service' | 'product'
  qty_available?: number  // Stock on hand
  virtual_available?: number  // Forecasted stock
  // ... more fields
}
```

### ProductCategory

```typescript
interface ProductCategory {
  id: number
  name: string
  parent_id?: [number, string]
  complete_name?: string  // Full path
}
```

### UnitOfMeasure

```typescript
interface UnitOfMeasure {
  id: number
  name: string
  category_id: [number, string]
  factor: number
  rounding: number
}
```

## Best Practices

1. **Reuse API instance** - Create once, use across all hooks
2. **Leverage caching** - React Query handles smart caching
3. **Real-time stock** - Stock refetches every 5 minutes automatically
4. **Barcode scanning** - Use `useProductByBarcode` for instant lookup
5. **Category filtering** - Use `child_of` domain for hierarchical categories

## License

MIT