Skip to main content
POST
/
api
/
v1
/
batches
/
receive
Create Batch (Receive Inventory)
curl --request POST \
  --url https://api.example.com/api/v1/batches/receive \
  --header 'Content-Type: application/json' \
  --data '
{
  "product_id": "<string>",
  "quantity": 123,
  "unit_cost": 123,
  "supplier_id": "<string>",
  "expiration_date": "<string>"
}
'
{
  "id": "<string>",
  "product_id": "<string>",
  "initial_quantity": 123,
  "available_quantity": 123,
  "unit_cost": 123,
  "purchase_date": "<string>",
  "expiration_date": {},
  "supplier_id": {},
  "entry_transaction_ref": {}
}

Overview

Receive inventory into the system by creating a new batch. This endpoint creates both a batch record and an entry movement for tracking purposes.

Authentication

Required Roles: admin or gestor
# backend/Product/Adapters/batch_controller.py:14
@router.route('/receive', methods=['POST'])
@require_role('admin', 'gestor')
def receive_purchase():
This endpoint requires Bearer token authentication. The consultor role cannot create batches.

Request Body

product_id
string
required
The UUID of the product to receive inventory for. Product must exist in the system.
quantity
integer
required
Number of units received. Must be greater than 0.
unit_cost
float
required
Cost per unit paid to the supplier. Used for COGS calculation.
supplier_id
string
Optional UUID of the supplier who provided this inventory.
expiration_date
string
Optional ISO 8601 datetime string. Batches with expiration dates are prioritized for sale (FEFO - First Expired, First Out).Format: YYYY-MM-DDTHH:MM:SSZ or YYYY-MM-DDTHH:MM:SS+00:00

Request Example

{
  "product_id": "a8b12c3d-4e5f-6789-0abc-def123456789",
  "quantity": 100,
  "unit_cost": 12.50,
  "supplier_id": "supplier-uuid-here",
  "expiration_date": "2026-12-31T23:59:59Z"
}

Response

id
string
Unique batch identifier (UUID)
product_id
string
Product UUID this batch belongs to
initial_quantity
integer
Total quantity received in this batch
available_quantity
integer
Current quantity available (equals initial_quantity at creation)
unit_cost
float
Cost per unit for this batch
purchase_date
string
ISO 8601 timestamp when the batch was created
expiration_date
string | null
ISO 8601 expiration date or null if not applicable
supplier_id
string | null
Supplier UUID or null if not provided
entry_transaction_ref
string | null
Reference to the associated entry movement

Response Example

{
  "id": "batch-uuid-abc123",
  "product_id": "a8b12c3d-4e5f-6789-0abc-def123456789",
  "initial_quantity": 100,
  "available_quantity": 100,
  "unit_cost": 12.50,
  "purchase_date": "2026-03-04T10:30:00+00:00",
  "expiration_date": "2026-12-31T23:59:59+00:00",
  "supplier_id": "supplier-uuid-here",
  "entry_transaction_ref": "movement-uuid-xyz789"
}

Error Responses

400 Bad Request - Missing Fields

{
  "error": "Missing required fields"
}
Returned when product_id, quantity, or unit_cost are not provided.

400 Bad Request - Invalid Quantity

{
  "detail": "Quantity must be greater than 0"
}

404 Not Found - Product Not Found

{
  "detail": "Product not found"
}

401 Unauthorized

Returned when authentication token is missing or invalid.

403 Forbidden

Returned when user role is consultor (insufficient permissions).

Implementation Details

The endpoint performs the following operations:
  1. Validates required fields and product existence
  2. Creates batch with generated UUID and current timestamp
  3. Creates entry movement for audit trail
  4. Returns batch data with 201 status
# backend/Product/Domain/stock_service.py:18-55
def register_entry(self, product_id: str, quantity: int, unit_cost: float, 
                   supplier_id: Optional[str] = None, expiration_date: Optional[datetime] = None) -> Tuple[Batch, Movement]:
    if quantity <= 0:
        raise HTTPException(status_code=400, detail="Quantity must be greater than 0")
    
    product = self.product_repo.get_by_id(product_id)
    if not product:
        raise HTTPException(status_code=404, detail="Product not found")

    # Create Batch
    batch_id = str(uuid.uuid4())
    new_batch = Batch(
        id=batch_id,
        product_id=product_id,
        initial_quantity=quantity,
        available_quantity=quantity,
        unit_cost=unit_cost,
        purchase_date=datetime.now(timezone.utc),
        expiration_date=expiration_date,
        supplier_id=supplier_id
    )
    self.repo.create_batch(new_batch)

    # Create Entry Movement
    mov_id = str(uuid.uuid4())
    new_movement = Movement(
        id=mov_id,
        product_id=product_id,
        type=MovementType.ENTRY,
        quantity=quantity,
        unit_price=unit_cost,
        total_price=unit_cost * quantity,
        reference_id=batch_id,
        notes="Purchase Entry"
    )
    self.repo.create_movement(new_movement)
    
    return new_batch, new_movement

Best Practices

Always provide expiration dates for perishable products. The system uses FEFO (First Expired, First Out) logic to prioritize batches nearing expiration.
Track supplier_id to enable supplier performance analysis and quality audits.

Build docs developers (and LLMs) love