Skip to main content

Overview

The Stakeholder Management system handles both customers and suppliers, providing a unified interface for managing business relationships, contact information, and transaction preferences.
Stakeholders are linked to inventory movements, enabling traceability for purchases (suppliers) and sales (customers).

Stakeholder Types

Customers

Individuals or businesses who purchase your products

Suppliers

Vendors who provide inventory to your business

Customer Management

Customers represent the buyers in your sales transactions.

Customer Data Model

class DocumentType(str, enum.Enum):
    RUC = "RUC"  # Tax ID
    DNI = "DNI"  # National ID
    OTRO = "OTRO"  # Other

class PaymentCondition(str, enum.Enum):
    CONTADO = "CONTADO"  # Cash
    CREDITO = "CREDITO"  # Credit

class Customer(Base, AuditableEntity):
    __tablename__ = "customers"

    id = Column(String(50), primary_key=True, index=True)
    nombre = Column(String(200), nullable=False, index=True)
    tipo_documento = Column(String(10), nullable=False, default=DocumentType.DNI)
    numero_documento = Column(String(50), unique=True, nullable=False, index=True)
    direccion = Column(String(300), nullable=True)
    telefono = Column(String(30), nullable=True)
    email = Column(String(150), unique=True, nullable=True)
    condicion_pago = Column(String(20), nullable=False, default=PaymentCondition.CONTADO)

    movements = relationship("Movement", back_populates="customer")

Customer Attributes

nombre
string
required
Customer name or business name
tipo_documento
enum
required
Document type: RUC, DNI, or OTRO
numero_documento
string
required
Unique document number (tax ID or national ID)
direccion
string
Customer address
telefono
string
Contact phone number
email
string
Email address (must be unique)
condicion_pago
enum
required
Payment terms: CONTADO (cash) or CREDITO (credit)

Document Types

For businesses and registered companies. Used for tax reporting and invoicing.
For individual customers using their national identification number.
For alternative document types like passports or foreign IDs.

Payment Conditions

CONTADO (Cash)

Payment required at time of sale

CREDITO (Credit)

Payment terms extended to customer

Customer UI Example

export const CustomersPage = () => {
    const { customers, fetchCustomers, createCustomer, updateCustomer, deleteCustomer } = useCustomerActions();
    const [searchQuery, setSearchQuery] = useState('');

    const handleSearch = (e) => {
        e.preventDefault();
        fetchCustomers(searchQuery);
    };

    return (
        <div>
            <h1>Gestión de <span className="text-blue-400">Clientes</span></h1>
            
            <form onSubmit={handleSearch}>
                <input
                    type="text"
                    placeholder="Buscar por nombre o documento..."
                    value={searchQuery}
                    onChange={(e) => setSearchQuery(e.target.value)}
                />
                <button type="submit">Buscar</button>
            </form>

            <table>
                <thead>
                    <tr>
                        <th>Nombre / Razón Social</th>
                        <th>Documento</th>
                        <th>Contacto</th>
                        <th>Condición Pago</th>
                        <th>Acciones</th>
                    </tr>
                </thead>
                <tbody>
                    {customers.map(c => (
                        <tr key={c.id}>
                            <td>{c.nombre}</td>
                            <td>{c.tipo_documento} {c.numero_documento}</td>
                            <td>
                                {c.email && <div>{c.email}</div>}
                                {c.telefono && <div>{c.telefono}</div>}
                            </td>
                            <td>
                                <span className={c.condicion_pago === 'CONTADO' 
                                    ? 'badge-green' : 'badge-orange'}>
                                    {c.condicion_pago}
                                </span>
                            </td>
                            <td>
                                <button onClick={() => handleOpenForm(c)}>Edit</button>
                                <button onClick={() => handleDelete(c.id)}>Delete</button>
                            </td>
                        </tr>
                    ))}
                </tbody>
            </table>
        </div>
    );
};

Supplier Management

Suppliers are vendors who provide inventory to your business.

Supplier Data Model

class Supplier(Base, AuditableEntity):
    __tablename__ = "suppliers"

    id = Column(String(50), primary_key=True, index=True)
    nombre = Column(String(200), nullable=False, index=True)
    tipo_documento = Column(String(10), nullable=False, default="RUC")
    numero_documento = Column(String(50), unique=True, nullable=False, index=True)
    direccion = Column(String(300), nullable=True)
    telefono = Column(String(30), nullable=True)
    email = Column(String(150), unique=True, nullable=True)
    plazo_entrega_dias = Column(Integer, nullable=True)
    condiciones_compra = Column(Text, nullable=True)

    movements = relationship("Movement", back_populates="supplier")

Supplier Attributes

nombre
string
required
Supplier business name
tipo_documento
string
required
Document type (typically “RUC” for businesses)
numero_documento
string
required
Unique tax identification number
plazo_entrega_dias
integer
Standard delivery time in days
condiciones_compra
text
Purchase terms and conditions (payment terms, minimum order quantities, etc.)

Supplier-Specific Fields

Track typical lead times for each supplier to better manage reordering schedules.Example: If Supplier A has a 7-day lead time, you can set reorder points accordingly.
Store important purchasing terms such as:
  • Minimum order quantities
  • Payment terms (e.g., “Net 30”)
  • Bulk discount structures
  • Return policies
Search across both customers and suppliers simultaneously:
@router.route('/search', methods=['GET'])
@require_role('admin', 'gestor', 'consultor')
def search_stakeholders():
    query = request.args.get('q', '').strip()
    stakeholder_type = request.args.get('type', 'all').lower()
    limit = int(request.args.get('limit', 10))
    
    service = StakeholderService(db)
    results = service.search_all_stakeholders(
        query=query, 
        limit=limit, 
        stakeholder_type=stakeholder_type
    )
    return jsonify(results), 200

Search Parameters

q
string
Search query (searches name and document number)
type
enum
default:"all"
Filter by type: all, customer, or supplier
limit
integer
default:"10"
Maximum number of results to return

Search Bar Component

<StakeholderSearchBar
    type="supplier"
    placeholder="Buscar proveedor..."
    onSelect={(id) => setFormData({ ...formData, supplier_id: id || '' })}
/>
The search bar provides autocomplete functionality, making it easy to link stakeholders to transactions.

Integration with Movements

Stakeholders are automatically linked to inventory movements:

Entry Movements (Suppliers)

batch, mov = stock_svc.register_entry(
    product_id=data.get('product_id'),
    quantity=int(data.get('quantity')),
    unit_cost=float(data.get('unit_cost')),
    supplier_id=data.get('supplier_id'),  # Link to supplier
    expiration_date=expiration_date
)

Exit Movements (Customers)

While the current implementation focuses on batch-level tracking, customer IDs can be associated with sales movements for CRM purposes.

CRUD Operations

Creating a Customer

Create Customer

POST /api/v1/customers/Creates a new customer record.
{
  "nombre": "Distribuidora ABC S.A.",
  "tipo_documento": "RUC",
  "numero_documento": "20123456789",
  "direccion": "Av. Principal 123, Lima",
  "telefono": "+51 1 234 5678",
  "email": "[email protected]",
  "condicion_pago": "CREDITO"
}

Creating a Supplier

Create Supplier

POST /api/v1/suppliers/Creates a new supplier record.
{
  "nombre": "Importadora XYZ S.R.L.",
  "tipo_documento": "RUC",
  "numero_documento": "20987654321",
  "direccion": "Jr. Comercio 456, Callao",
  "telefono": "+51 1 987 6543",
  "email": "[email protected]",
  "plazo_entrega_dias": 7,
  "condiciones_compra": "Net 30, MOQ: 100 units"
}

Validation Rules

Document numbers (numero_documento) must be unique across all customers and suppliers of the same type.

Required Fields

  • nombre (name)
  • tipo_documento (document type)
  • numero_documento (document number)
  • condicion_pago (customers only)

Unique Constraints

  • numero_documento must be unique
  • email must be unique (if provided)

Role-Based Access

Stakeholder management respects user roles:
Full CRUD access for both customers and suppliers
Full CRUD access for both customers and suppliers
Read-only access, can search and view but cannot create/edit/delete

Best Practices

Complete Profiles

Fill in all contact information for better communication and record-keeping

Verify Documents

Ensure document numbers are accurate for tax compliance and invoicing

Set Payment Terms

Clearly define payment conditions to avoid confusion

Track Supplier Terms

Record delivery times and purchase conditions for better inventory planning

Traceability Benefits

Linking stakeholders to movements enables:
1

Supplier Performance Analysis

Track which suppliers provide the best quality, pricing, and delivery times
2

Customer Purchase History

View complete purchase history for each customer
3

Batch Recall Management

Quickly identify all customers who received products from a specific supplier batch
4

Financial Reporting

Generate supplier and customer reports for accounts payable/receivable

Stakeholder Form

The unified form component handles both customers and suppliers:
<StakeholderForm
    type="customer"  // or "supplier"
    initialData={editingCustomer}
    onSave={handleSave}
    onCancel={handleCloseForm}
    loading={loading}
/>
The form automatically adjusts fields based on stakeholder type, showing payment conditions for customers and delivery terms for suppliers.

Search and Filter

The customer and supplier pages include search functionality:
const [searchQuery, setSearchQuery] = useState('');

const handleSearch = (e) => {
    e.preventDefault();
    fetchCustomers(searchQuery);  // or fetchSuppliers(searchQuery)
};

<form onSubmit={handleSearch}>
    <input
        type="text"
        placeholder="Buscar por nombre o documento..."
        value={searchQuery}
        onChange={(e) => setSearchQuery(e.target.value)}
    />
    <button type="submit">Buscar</button>
</form>

Stock Movements

Link stakeholders to transactions

Batch Tracking

Supplier traceability for batches

API Reference

Complete API documentation

Build docs developers (and LLMs) love