# Job Card System in ERPNext Manufacturing: How to Track Production Time

> Understand the ERPNext Job Card system to track manufacturing production time effectively. Capture employee time logs and prevent scheduling conflicts with capacity constraints.

- Repository: [Frappe/erpnext](https://github.com/frappe/erpnext)
- Tags: how-to-guide
- Published: 2026-05-20

---

**The Job Card system in manufacturing is a document-based workflow that tracks individual production operations against Work Orders, capturing granular employee time logs while enforcing workstation capacity constraints to prevent scheduling conflicts.**

The Job Card serves as the operational backbone of ERPNext's manufacturing module, bridging Work Orders, Bill of Materials (BOM), and shop floor resources. As implemented in the `frappe/erpnext` repository, this system enables discrete manufacturing environments to monitor production time, material transfers, and employee productivity through a structured status lifecycle defined in [`erpnext/manufacturing/doctype/job_card/job_card.py`](https://github.com/frappe/erpnext/blob/main/erpnext/manufacturing/doctype/job_card/job_card.py).

## What Is a Job Card in ERPNext?

A **Job Card** represents a single operation required to fulfill a Work Order. It links the Work Order, BOM, specific Workstation, assigned employees, and the time logs that record exactly how long each step required. Each card moves through a defined status lifecycle managed by the `status` field:

- **Open**: Created but not started
- **Work In Progress**: Materials transferred and time logging active
- **Material Transferred**: All required raw materials moved to the Work-In-Progress (WIP) warehouse
- **On Hold**: Paused by the user via the `is_paused` flag
- **Completed**: Production quantity matches the `for_quantity` field (including process loss)
- **Cancelled**: Discarded via `on_discard`

The class definition resides in **[`erpnext/manufacturing/doctype/job_card/job_card.py`](https://github.com/frappe/erpnext/blob/main/erpnext/manufacturing/doctype/job_card/job_card.py)**, which declares critical fields including `operation`, `workstation`, `time_logs`, and `status`.

## How Production Time Tracking Works

### The Time Log Data Structure

Production time is stored in a child table called **Job Card Time Log** (`time_logs`). The schema lives in **[`erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.py`](https://github.com/frappe/erpnext/blob/main/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.py)** and tracks:

- `from_time` and `to_time`: Start and end timestamps
- `employee`: The worker assigned
- `time_in_mins`: Calculated duration
- `completed_qty`: Units produced during this log entry

### Recording Time via API Methods

Operators log time through the `add_time_log` method or the whitelisted `make_time_log` endpoint. These functions:

1. Create a new row in the `time_logs` table or update an existing entry
2. Calculate duration using `time_diff_in_minutes`
3. Trigger `validate_time_logs` to ensure chronological integrity (From time must precede To time) and check for overlapping entries

### Aggregation and Validation

After each log entry, the system runs `validate_time_logs(save=True)` to aggregate `total_time_in_mins` and store it on the parent Job Card. The method `set_expected_and_actual_time` identifies the earliest `from_time` and latest `to_time` across all logs, populating the `actual_start_date` and `actual_end_date` fields while computing total required minutes for the operation.

## Workstation Capacity and Overlap Prevention

ERPNext prevents double-booking of workstations through capacity validation logic in [`job_card.py`](https://github.com/frappe/erpnext/blob/main/job_card.py). The `get_overlap_for` function gathers all existing logs that intersect with a proposed new time window. The `has_overlap` method then evaluates the workstation's production capacity (default value of **1**) and raises `OverlapError` if the simultaneous usage would exceed this limit.

This ensures that two employees cannot be assigned to the same workstation at the same time unless the capacity setting explicitly allows it.

## Pausing and Resuming Production

The system supports interrupted workflows through two whitelisted methods exposed to the UI:

- **`pause_job`**: Sets `is_paused = 1` and creates a zero-quantity log ending at the pause moment
- **`resume_job`**: Clears the pause flag and generates a new log starting when work resumes

These methods allow accurate tracking of active production time versus idle time.

## Integration with Work Orders and Stock

When a Job Card is submitted (`on_submit`), the system:

1. Validates required quality inspections
2. Updates the linked Work Order via `update_work_order`
3. Records material transfers through `set_transferred_qty`
4. Creates semi-finished goods movements via `make_stock_entry_for_semi_fg_item`, which generates a **Manufacture** Stock Entry to move items forward after completion

## Production Reporting and Analytics

Aggregated metrics are exposed through the **Job Card Summary** report located at **[`erpnext/manufacturing/report/job_card_summary/job_card_summary.py`](https://github.com/frappe/erpnext/blob/main/erpnext/manufacturing/report/job_card_summary/job_card_summary.py)**. This report pulls `total_time_in_mins`, `total_completed_qty`, and related fields to provide production efficiency metrics across operations.

## Practical Implementation Examples

### Creating a Job Card from a Work Order

```python
import frappe

# Assume a Work Order already exists

wo_name = "WO‑0001"

# Create a new Job Card for the first operation

job_card = frappe.get_doc({
    "doctype": "Job Card",
    "work_order": wo_name,
    "operation": "Cutting",
    "for_quantity": 100,
    "company": "My Company",
    "workstation_type": "Cutting Station"
})
job_card.insert()
job_card.submit()
print("Created Job Card:", job_card.name)

```

### Starting a Timer for an Employee

```javascript
frappe.call({
    method: "erpnext.manufacturing.doctype.job_card.job_card.start_timer",
    args: {
        job_card_id: "JC‑00001",
        start_time: frappe.datetime.now_datetime(),
        employees: [{ employee: "EMP‑001" }]
    },
    callback: function(r) {
        frappe.msgprint("Timer started for employee.");
    }
});

```

### Completing a Job Card via REST API

```bash
curl -X POST https://erp.mycompany.com/api/method/erpnext.manufacturing.doctype.job_card.job_card.complete_job_card \
     -d "job_card_id=JC-00001" \
     -d "end_time=2026-05-20 15:30:00" \
     -d "qty=100" \
     -d "auto_submit=1"

```

### Querying Production Time Server-Side

```python
jc = frappe.get_doc("Job Card", "JC-00001")
print("Total minutes spent:", jc.total_time_in_mins)
print("Started at:", jc.actual_start_date, "Ended at:", jc.actual_end_date)

```

## Summary

- The **Job Card** is the discrete operational unit in ERPNext manufacturing, linking Work Orders to specific workstations and employees
- Production time is captured in the **`Job Card Time Log`** child table with automatic aggregation into `total_time_in_mins`
- **Workstation capacity** validation prevents scheduling conflicts through `get_overlap_for` and `has_overlap`
- **Pause and resume** functionality allows accurate tracking of productive versus non-productive time
- Completion triggers stock entries and Work Order updates via `make_stock_entry_for_semi_fg_item` and `update_work_order`

## Frequently Asked Questions

### What is the difference between a Work Order and a Job Card in ERPNext?

A **Work Order** represents the entire production request for a finished good, while a **Job Card** represents a single operation within that routing. One Work Order can generate multiple Job Cards (one per operation), each tracking specific employees, workstations, and time logs for that discrete step.

### How does ERPNext prevent two employees from logging time on the same workstation simultaneously?

The system checks workstation capacity using `has_overlap` in [`job_card.py`](https://github.com/frappe/erpnext/blob/main/job_card.py). If the number of concurrent time logs exceeds the workstation's capacity (default **1**), the system raises an `OverlapError`. This enforces sequential or capacity-planned usage of constrained resources.

### Can a Job Card be paused mid-operation, and how does that affect time tracking?

Yes. Calling the whitelisted `pause_job` method sets `is_paused = 1` and closes the current time log at the pause timestamp. When `resume_job` is invoked, a new log begins. This creates a clean separation between productive time and idle time, ensuring accurate efficiency reporting without distorting the total operation duration.

### Where can managers view aggregated production time across multiple Job Cards?

Navigate to **Manufacturing > Reports > Job Card Summary**, which executes the report defined in **[`erpnext/manufacturing/report/job_card_summary/job_card_summary.py`](https://github.com/frappe/erpnext/blob/main/erpnext/manufacturing/report/job_card_summary/job_card_summary.py)**. This report aggregates `total_time_in_mins`, `total_completed_qty`, and actual versus expected times across filtered Job Cards for shop floor analysis.