npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kodeme-io/next-core-partner

v0.3.2

Published

Partner/Customer management module for Next.js + Odoo applications

Readme

@next-odoo/partner

Partner/Customer management module for Next.js + Odoo applications with Canvas pipeline, NOO workflow, and approval management.

Features

  • Customer Management - CRUD operations for partners/customers
  • Canvas Pipeline - Prospect to customer conversion with stages
  • NOO Workflow - New Outlet Opening application process
  • Approval System - Tier validation for taking_order customers
  • React Query Hooks - Optimistic updates and caching
  • Type Safety - Full TypeScript support
  • Operating Unit - Multi-branch/region filtering
  • Search - Fuzzy search by name, ref, or mobile

Installation

pnpm add @next-odoo/partner @next-odoo/odoo-api @tanstack/react-query

Usage

Setup

import { createOdooClient } from '@next-odoo/odoo-api'
import { createPartnerAPI } from '@next-odoo/partner'

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

// Create Partner API
const partnerAPI = createPartnerAPI(odoo)

Query Hooks

Get All Customers

import { usePartners } from '@next-odoo/partner'

export function CustomerList() {
  const { data: customers, isLoading, error } = usePartners(partnerAPI, {
    domain: [['active', '=', true]],
    limit: 50,
    order: 'name ASC'
  })

  if (isLoading) return <div>Loading...</div>
  if (error) return <div>Error: {error.message}</div>

  return (
    <ul>
      {customers?.map(customer => (
        <li key={customer.id}>{customer.name}</li>
      ))}
    </ul>
  )
}

Get Single Customer

import { usePartner } from '@next-odoo/partner'

export function CustomerDetail({ id }: { id: number }) {
  const { data: customer } = usePartner(partnerAPI, id)

  if (!customer) return null

  return (
    <div>
      <h1>{customer.name}</h1>
      <p>Email: {customer.email}</p>
      <p>Phone: {customer.mobile}</p>
      <p>Type: {customer.customer_type}</p>
    </div>
  )
}

Get Canvas Customers

import { useCanvasCustomers } from '@next-odoo/partner'

export function CanvasDashboard() {
  const { data: canvasCustomers } = useCanvasCustomers(partnerAPI)

  return (
    <div>
      <h2>Canvas Customers ({canvasCustomers?.length})</h2>
      {/* Render canvas pipeline */}
    </div>
  )
}

Get Prospects

import { useProspects } from '@next-odoo/partner'

export function ProspectList() {
  const { data: prospects } = useProspects(partnerAPI)

  return <div>Total Prospects: {prospects?.length}</div>
}

Get Customers Pending Approval

import { usePendingApproval } from '@next-odoo/partner'

export function ApprovalQueue() {
  const { data: pending } = usePendingApproval(partnerAPI)

  return (
    <div>
      <h2>Pending Approval ({pending?.length})</h2>
      {pending?.map(customer => (
        <CustomerApprovalCard key={customer.id} customer={customer} />
      ))}
    </div>
  )
}

Get Canvas Stages

import { useCanvasStages } from '@next-odoo/partner'

export function CanvasPipeline() {
  const { data: stages } = useCanvasStages(partnerAPI)

  return (
    <div className="flex gap-4">
      {stages?.map(stage => (
        <div key={stage.id} className="flex-1">
          <h3>{stage.name}</h3>
          {/* Render customers in this stage */}
        </div>
      ))}
    </div>
  )
}

Mutation Hooks

Create Customer

import { useCreatePartner } from '@next-odoo/partner'

export function CreateCustomerForm() {
  const createCustomer = useCreatePartner(partnerAPI)

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

    try {
      const customerId = await createCustomer.mutateAsync({
        name: 'New Customer',
        email: '[email protected]',
        mobile: '+1234567890',
        customer_type: 'canvas',
        is_canvas_customer: true
      })

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

  return (
    <form onSubmit={handleSubmit}>
      {/* Form fields */}
      <button type="submit" disabled={createCustomer.isPending}>
        {createCustomer.isPending ? 'Creating...' : 'Create Customer'}
      </button>
    </form>
  )
}

Update Customer

import { useUpdatePartner } from '@next-odoo/partner'

export function EditCustomer({ customerId }: { customerId: number }) {
  const updateCustomer = useUpdatePartner(partnerAPI)

  const handleUpdate = async () => {
    await updateCustomer.mutateAsync({
      id: customerId,
      updates: {
        phone: '+0987654321',
        email: '[email protected]'
      }
    })
  }

  return <button onClick={handleUpdate}>Update Customer</button>
}

Convert to Canvas

import { useConvertToCanvas } from '@next-odoo/partner'

export function ConvertButton({ customerId, initialStageId }: Props) {
  const convertToCanvas = useConvertToCanvas(partnerAPI)

  const handleConvert = async () => {
    await convertToCanvas.mutateAsync({
      customerId,
      stageId: initialStageId
    })
  }

  return (
    <button onClick={handleConvert}>
      Convert to Canvas
    </button>
  )
}

Move to Stage

import { useMoveToStage } from '@next-odoo/partner'

export function MoveStageButton({ customerId, newStageId }: Props) {
  const moveToStage = useMoveToStage(partnerAPI)

  return (
    <button onClick={() => moveToStage.mutate({ customerId, stageId: newStageId })}>
      Move to Next Stage
    </button>
  )
}

Approve Customer

import { useApproveCustomer } from '@next-odoo/partner'

export function ApproveButton({ customerId }: { customerId: number }) {
  const approveCustomer = useApproveCustomer(partnerAPI)

  const handleApprove = async () => {
    await approveCustomer.mutateAsync({
      customerId,
      notes: 'Approved after verification'
    })
  }

  return <button onClick={handleApprove}>Approve</button>
}

Reject Customer

import { useRejectCustomer } from '@next-odoo/partner'

export function RejectButton({ customerId }: { customerId: number }) {
  const rejectCustomer = useRejectCustomer(partnerAPI)

  const handleReject = async () => {
    await rejectCustomer.mutateAsync({
      customerId,
      reason: 'Incomplete information'
    })
  }

  return <button onClick={handleReject}>Reject</button>
}

Search Customers

import { useSearchCustomers } from '@next-odoo/partner'
import { useState } from 'react'

export function CustomerSearch() {
  const [query, setQuery] = useState('')
  const { data: results } = useSearchCustomers(partnerAPI, query, 20)

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

NOO (New Outlet Opening)

Get NOO Candidates

import { useNOOCandidates } from '@next-odoo/partner'

export function NOODashboard() {
  const { data: candidates } = useNOOCandidates(partnerAPI)

  return (
    <div>
      <h2>NOO Applications ({candidates?.length})</h2>
      {/* Render NOO applications */}
    </div>
  )
}

Create NOO Application

import { useCreateNOO } from '@next-odoo/partner'

export function CreateNOOForm({ partnerId }: { partnerId: number }) {
  const createNOO = useCreateNOO(partnerAPI)

  const handleSubmit = async () => {
    await createNOO.mutateAsync({
      partner_id: partnerId,
      business_type: 'retail',
      estimated_monthly_volume: 10000000,
      investment_capacity: 50000000,
      business_experience: '5 years in retail'
    })
  }

  return <button onClick={handleSubmit}>Submit NOO Application</button>
}

API Reference

PartnerAPI

Direct API methods (used by hooks):

const partnerAPI = createPartnerAPI(odooClient)

// Query methods
await partnerAPI.getCustomers(options)
await partnerAPI.getCustomerById(id)
await partnerAPI.getCanvasCustomers()
await partnerAPI.getTakingOrderCustomers()
await partnerAPI.getProspects()
await partnerAPI.getPendingApproval()
await partnerAPI.getCanvasStages()
await partnerAPI.searchCustomers(query, limit)

// Mutation methods
await partnerAPI.createCustomer(data)
await partnerAPI.updateCustomer(id, updates)
await partnerAPI.deleteCustomer(id)
await partnerAPI.convertToCanvas(customerId, stageId)
await partnerAPI.moveToStage(customerId, stageId)
await partnerAPI.approveCustomer(customerId, notes)
await partnerAPI.rejectCustomer(customerId, reason)
await partnerAPI.requestValidation(customerId)

// NOO methods
await partnerAPI.getNOOCandidates()
await partnerAPI.createNOOApplication(data)
await partnerAPI.approveNOO(nooId)
await partnerAPI.rejectNOO(nooId, reason)

Types

Customer

interface Customer {
  id: number
  name: string
  ref?: string
  email?: string
  phone?: string
  mobile?: string
  street?: string
  city?: string
  customer_type: 'canvas' | 'taking_order'
  approval_status?: 'draft' | 'pending_approval' | 'approved' | 'rejected'
  is_canvas_customer: boolean
  canvas_stage_id?: number
  latitude?: number
  longitude?: number
  // ... many more fields
}

CanvasStage

interface CanvasStage {
  id: number
  name: string
  sequence: number
  is_initial_stage: boolean
  is_final_stage: boolean
}

CandidateNOO

interface CandidateNOO {
  id: number
  partner_id: number
  business_type: 'retail' | 'wholesaler' | 'distributor' | 'other'
  state: 'draft' | 'submitted' | 'under_review' | 'approved' | 'rejected' | 'converted'
  // ... more fields
}

Best Practices

  1. Reuse API instance - Create once, use across all hooks
  2. Use query keys - Leverage partnerKeys for cache invalidation
  3. Handle loading states - Always check isLoading and error
  4. Optimistic updates - React Query handles this automatically
  5. Stale time - Configured per hook (2-30 minutes)

License

MIT