Labor & Downtime
Record who worked on which work order and for how long, and log the periods when a machine or station was not producing — two shop-floor logs that feed accurate actual production cost.
One time entry per employee per work order/operation. Clock In → Clock Out
gives hours; hours × hourly rate gives labor cost. Open entries show an Active badge until
you close them. Route: production/LaborTracking.
One event per stoppage against a work center. Categorised by type, described
by a reason, timed Start → End for a duration in minutes, with an optional maintenance cost. Open
events show an Ongoing badge. Route: production/LaborTracking/downtime.
manufacturing_labor, and
require the manufacturing module to be enabled. Without either, you are redirected away.
See Actions & permissions below and the full
Permissions & Roles matrix.
Labor time entries
A labor time entry answers four questions: which work order (and optional operation),
who worked, how long (Clock In to Clock Out), and at what rate. From those,
ERPat computes the hours worked and the labor cost — you never type those two
directly. The Labor Tracking list page (production/LaborTracking) shows every
entry across all work orders and is the entry point to log, edit, delete, and jump to Downtime.
The Labor Tracking list
The page header carries a Log Labor button (opens the Log Labor Entry modal) and a
Downtime button (links to the Downtime Tracking page). A Labor Type filter dropdown
narrows the table. The DataTable #labor-table shows these columns:
| Column | Meaning | Notes |
|---|---|---|
| Work Order | The order the labor was booked against. | Shown as order_number - product_name. |
| Operation | The routing step, if one was chosen. | May be blank when no operation was selected. |
| Employee | The staff member who performed the labor. | First + last name. |
| Type | Labor Type classification. | Direct / Indirect / Setup / Rework (see the legend). |
| Clock In | When work started. | Date-and-time. |
| Clock Out | When work finished. | Shows an Active badge when no clock-out has been recorded. |
| Hours | Computed elapsed hours. | Displayed as X.XX hrs; 0 while Active. |
| Rate | The hourly rate used for costing. | Per-hour amount. |
| Cost | Computed labor cost. | Hours × Rate; falls back to that product if the stored cost is empty. |
| Actions (bars) | Row menu. | Pencil to edit, X to delete. |
list_data endpoint's order_id option.
Log Labor Entry — every field
The Log Labor Entry modal (production/LaborTracking/modal_form) creates or edits one
entry. Required fields carry a red asterisk and client-side data-rule-required.
| Field | Meaning | Required | Notes |
|---|---|---|---|
order_id — Work Order |
The manufacturing work order the labor was performed against. | Required | Select2 dropdown. In the modal it lists all work orders as order_number - product_name. Joins to show order number and product name in the list. |
operation_id — Operation |
The specific order operation / routing step worked on. | Optional | Select2 dropdown sourced from manufacturing_order_operations (operation name); a blank / - choice is allowed. Stored as null when left blank. |
employee_id — Employee |
The staff member who performed the labor. | Required | Select2 dropdown of active staff (first + last name). Sourced from users where status=active and user_type=staff. |
labor_type — Labor Type |
Classifies the nature of the labor time. | Optional | Dropdown: Direct / Indirect / Setup / Rework. Defaults to direct if not chosen. DB enum('direct','indirect','setup','rework'). |
clock_in — Clock In |
Date-and-time the worker started. | Required | HTML datetime-local input. Used to derive work_date (the Y-m-d of clock-in) and to compute hours worked. |
clock_out — Clock Out |
Date-and-time the worker finished. | Optional | datetime-local. If blank, the entry is Active and hours stay 0. Stored null when empty. |
hourly_rate — Hourly Rate |
Pay/cost rate per hour used to compute labor cost. | Optional | Number input, step 0.01, default 0.00. DB decimal(10,2). |
notes — Notes |
Free-text remarks about the labor entry. | Optional | Textarea, 3 rows. |
Computed & derived values (not on the form)
These are calculated on save — you never type them, and they are what flows into costing.
| Field | Meaning | Required | Notes |
|---|---|---|---|
hours_worked — Hours |
Computed elapsed hours between clock in and clock out. | Computed | = round((clock_out − clock_in) / 3600, 2). Shown in the list as X.XX hrs. DB decimal(5,2). |
labor_cost — Cost |
Computed cost of the labor entry. | Computed | = round(hours_worked × hourly_rate, 2). The list falls back to hours × rate if the stored cost is empty. DB decimal(15,2). |
work_date — (not on form) |
The calendar date of the labor, used for filtering/sorting. | Derived | Auto-set to the date portion of clock_in (or today if clock-in is missing). Not a visible form field. |
How labor rolls into actual cost
When you save a labor entry, ERPat recomputes Hours from Clock In / Clock Out and Cost as Hours × Hourly Rate, then stores both on the entry. Production Costing's actual-cost model reads directly from the labor-tracking table, so the labor cost you record here is summed into the actual production cost of the associated work order. In short:
Labor entry lifecycle
A labor entry is Active until it has a Clock Out; once closed, hours and cost are computed. An entry can be re-edited (recomputing hours/cost) or soft-deleted.
deleted = 1); the row disappears from the list but is not physically removed.Labor Type legend
The Type column and filter classify each entry. These are the exact enum values.
labor_type = direct). The default when none is chosen.labor_type = indirect).labor_type = setup).labor_type = rework).SOP — Record labor time against a work order
manufacturing_labor permission; the Work Order and (optionally) its Operation already
exist.
Open Labor Tracking
Navigate to Production → Labor Tracking (
production/LaborTracking).Start a new entry
Click Log Labor to open the entry modal with empty fields.
Pick the work order and operation
Choose the Work Order (required) and, if relevant, the Operation. The modal lists all work orders, not just in-progress ones.
Pick the employee
Choose the Employee (required) — only active staff appear in the dropdown.
Set the labor type
Choose Labor Type (Direct / Indirect / Setup / Rework). Direct is the default.
Enter the times
Enter Clock In (required). Enter Clock Out if the work is finished; otherwise leave it blank to record an Active entry.
Enter the rate
Enter the Hourly Rate so the labor cost can be computed.
Add notes and save
Add Notes if useful, then click Save.
Verify the totals
Confirm the computed Hours and Cost in the table. If you left Clock Out blank, re-open the entry later to close it.
Recording an Active entry and closing it later
Open a new entry
In Labor Tracking, click Log Labor.
Save without a Clock Out
Fill Work Order, Employee, Labor Type, and Clock In, leaving Clock Out blank, then Save.
Confirm it is Active
The entry shows an Active badge with 0 hours and 0 cost.
Edit when the shift ends
When the worker finishes, click the pencil to edit the entry.
Enter Clock Out and save
Enter the Clock Out time and Save; Hours and Cost are computed automatically.
clock_in and clock_out actions for the current logged-in user.
Clock in creates an entry against a work order (optionally an operation) stamping
clock_in = now, work_date = today, labor type direct, and an
hourly rate from input or 0 — leaving it Active. Clock out updates that entry with
clock_out = now and computes hours and cost. Both use the same
manufacturing_labor permission.
Downtime logging
A downtime event records a period when a work center (machine or station) was not producing:
which center, why (a type + a free-text reason), how long (Start → End), and
any maintenance cost. The Downtime Tracking list (production/LaborTracking/downtime)
shows every event and is the entry point to log, edit, delete, and jump back to Labor.
The Downtime Tracking list
The header carries a Log Downtime button (opens the Log Downtime modal) and a Labor
button (links back to Labor Tracking). Two filter dropdowns are available — Type and
Status. The DataTable #downtime-table shows these columns:
| Column | Meaning | Notes |
|---|---|---|
| Work Center | The machine/station that was down. | Shown by name/code. |
| Type | Downtime category. | Breakdown / Maintenance / Setup / Material Shortage / Other (see the legend). |
| Reason | Free-text description of why the center was down. | Its own column. |
| Start Time | When the downtime began. | Date-and-time. |
| End Time | When it ended / the machine came back up. | Shows an Ongoing badge when no end time has been recorded. |
| Duration (min) | Computed length in minutes. | 0 while ongoing. |
| Cost | Maintenance cost incurred to resolve. | Per-event amount. |
| Reported By | The user who logged the event. | Set on create; not editable. |
| Actions (bars) | Row menu. | Pencil to edit, X to delete. |
Log Downtime — every field
The Log Downtime modal (production/LaborTracking/downtime_modal_form) creates or edits
one event. Required fields carry a red asterisk and data-rule-required.
| Field | Meaning | Required | Notes |
|---|---|---|---|
work_center_id — Work Center |
The machine / work station that was down. | Required | Select2 dropdown of active work centers (code - name). Sourced from manufacturing_work_centers where is_active = 1. |
downtime_type — Type |
Category / root-cause bucket of the downtime. | Required | Dropdown: Breakdown / Maintenance / Setup / Material Shortage / Other. The modal defaults the dropdown to breakdown; if somehow blank, save defaults to other. DB enum('breakdown','maintenance','setup','material_shortage','other'). |
reason — Reason |
Description of why the work center was down. | Required | Textarea, 2 rows. Shown as its own column in the list. |
start_time — Start Time |
When the downtime began. | Required | datetime-local. Derives downtime_date and the duration. |
end_time — End Time |
When the downtime ended / the machine came back up. | Optional | datetime-local. If blank, the event is Ongoing and status stays open; if filled, status becomes resolved and the current user is recorded as resolver. Stored null when empty. |
maintenance_cost — Maintenance Cost |
Cost incurred to fix / resolve the downtime. | Optional | Number input, step 0.01, default 0.00. DB decimal(15,2). |
notes — Notes |
Free-text remarks about the downtime. | Optional | Textarea, 2 rows. |
Computed, derived & auto fields (not directly entered)
| Field | Meaning | Required | Notes |
|---|---|---|---|
duration_minutes — Duration (min) |
Computed length of the downtime in minutes. | Computed | = round((end_time − start_time) / 60, 0). 0 while ongoing. DB int. |
downtime_date — (not on form) |
Calendar date of the downtime, used for filter/sort. | Derived | Auto-set to the date portion of start_time (or today). Not a visible field. |
reported_by — Reported By |
The user who logged the downtime. | Auto | Set to the logged-in user on create only; shown in the list. Not editable. |
resolved_by — (not shown in list) |
The user who marked the downtime resolved. | Auto | Set to the logged-in user whenever an End Time is provided on save. |
production_loss_quantity — (DB only) |
Quantity of production lost due to downtime. | Not used | Column exists in the DB (decimal(15,4)) but is NOT exposed on the downtime form or list — it always defaults to 0. |
Downtime lifecycle
A downtime event's status is driven entirely by the End Time. With no End Time it is Open (Ongoing); provide an End Time on save and it flips to Resolved. There is no explicit "resolve" button.
status = open. Duration is 0; shown with an orange Ongoing badge in the End Time column.status = resolved, duration computed, resolver recorded. Can be re-edited; clearing the End Time on save reverts it to Open.deleted = 1); the row disappears but is not physically removed.Downtime Type legend (exact categories)
The Type column and filter classify each event into one of these buckets. These are the exact
enum values — enum('breakdown','maintenance','setup','material_shortage','other').
breakdown). The modal's default type.maintenance).setup).material_shortage).other). Also the fallback if the type saves blank.SOP — Log and resolve a work-center downtime
manufacturing_labor permission; the affected Work
Center exists and is active (is_active = 1) — inactive centers won't appear in
the dropdown.
Open Downtime Tracking
Go to Production → Downtime Tracking (or click Downtime from Labor Tracking).
Start a new event
Click Log Downtime.
Pick the work center
Select the Work Center (required) that went down.
Choose the type
Select the Type (required; Breakdown by default).
Describe the reason
Enter the Reason (required) describing the stoppage.
Record the start
Enter the Start Time (required). Leave End Time blank while the stoppage is ongoing, then Save — the record is Open / Ongoing and stamped with you as reporter.
Resume production
Once the machine is back up, edit the record, enter the End Time and Maintenance Cost, and Save.
Confirm it is Resolved
The record flips to Resolved; Duration (min) is computed and you are recorded as the resolver.
Reviewing open downtimes
Open Downtime Tracking
Navigate to the Downtime list.
Filter to what matters
Use the Status filter to show only Open events, and the Type filter to focus on e.g. Breakdown / Maintenance.
Prioritise by impact
Check the Duration (min) and Cost columns to prioritise which stoppages to resolve first.
SOP — Log labor and downtime for a shift
At the end of a shift, a supervisor typically records the operators' labor and any machine stoppages together. This end-to-end procedure combines both logs.
manufacturing_labor.
The work orders and their operations exist; the affected work centers are active.
Open Labor Tracking
Go to Production → Labor Tracking. This is your starting point for the shift's time entries.
Log each operator's labor
For every operator, click Log Labor, choose the Work Order (and Operation where relevant), the Employee, and the Labor Type. Enter Clock In and Clock Out and the Hourly Rate, then Save.
Leave in-progress work Active
If someone is still working at handover, save their entry with a blank Clock Out — it stays Active and the next shift closes it.
Verify the labor totals
Scan the Hours / Rate / Cost columns; every closed entry should show computed hours and cost.
Switch to Downtime
Click the Downtime button to open the Downtime Tracking page.
Log each stoppage
For every machine stoppage during the shift, click Log Downtime, choose the Work Center and Type, write the Reason, and enter the Start Time. If the stoppage is resolved, add the End Time and Maintenance Cost; if still down, leave End Time blank.
Confirm status
Resolved events show a computed Duration; ongoing ones show the Ongoing badge and status Open, reported by you.
Hand off cost review
The labor costs and stoppages you recorded now feed Production Costing; a manager can compare standard vs. actual cost per work order.
Actions
Every action below is gated by the single manufacturing_labor permission (see
Permissions). Saves run inline form validation only — there is no
multi-step confirmation except on delete.
| Action | Effect | Confirmation |
|---|---|---|
| Log Labor (open create modal) | Opens the Log Labor Entry modal with empty fields to create a new labor time record. | None |
| Edit labor (pencil) | Opens the Log Labor Entry modal pre-filled with the selected record for update. | None |
| Save labor | Creates or updates the entry. Recomputes hours from clock in/out and cost = hours × hourly rate; sets work_date from clock in; defaults labor type to direct. On create sets uuid and created_at; always sets updated_at. | None (inline validation only) |
| Delete labor (X) | Soft-deletes the entry (deleted = 1); the row disappears from the list. | Standard delete-confirmation dialog |
| Clock in (self) | Creates a new entry for the current user against a work order (and optional operation), stamping clock_in = now, work_date = today, labor type direct, rate from input or 0. No clock out — the entry shows Active. | None |
| Clock out (self) | Updates an existing entry: sets clock_out = now, computes hours from the stored clock in, and cost = hours × stored rate; stamps updated_at. | None |
| Downtime (nav button) | Navigates from Labor Tracking to the Downtime Tracking page. | None |
| Labor (nav button) | Navigates from Downtime Tracking back to Labor Tracking. | None |
| Log Downtime (open create modal) | Opens the Log Downtime modal with empty fields to create a new event. | None |
| Edit downtime (pencil) | Opens the Log Downtime modal pre-filled with the selected record for update. | None |
| Save downtime | Creates or updates the event. Computes duration from start/end; sets downtime_date from start; defaults type to other if blank. If End Time is present: status resolved and resolver = current user; else status open. On create sets uuid, reporter = current user, created_at; always sets updated_at. | None (inline validation only) |
| Delete downtime (X) | Soft-deletes the event (deleted = 1); the row disappears from the list. | Standard delete-confirmation dialog |
Validation & system messages
Required fields are enforced both client-side (red asterisk, data-rule-required) and on
the server. The exact server rules and messages:
| Action | Server rules |
|---|---|
| Save labor | id numeric; order_id required & numeric; employee_id required & numeric; clock_in required. |
| Delete labor | id required & numeric. |
| Clock in (self) | order_id required & numeric. |
| Clock out (self) | id required & numeric. |
| Save downtime | id numeric; work_center_id required & numeric; reason required; start_time required. |
| Delete downtime | id required & numeric. |
record_saved); deleting shows
“Record deleted successfully.” (record_deleted). The self-service clock actions
show the hard-coded “Clocked in successfully” and “Clocked out. Hours: <computed
hours>”.
error_occurred); a record that can't be removed shows “This record cannot be
deleted.” (record_cannot_be_deleted). Deletes always go through the standard
delete-confirmation dialog first.
Permissions
Access to the whole feature — viewing and every create/edit/delete/clock action for both labor and downtime, plus the sidebar menu item — is controlled by one permission key.
| Permission / gate | Meaning | Notes |
|---|---|---|
manufacturing_labor |
The single permission enforced by the LaborTracking controller. | Gates viewing and all create/edit/delete/clock actions for both labor and downtime, plus the sidebar menu item. Category “Manufacturing: Labor Tracking”, default level module. Constructor: with_permission('manufacturing_labor', 'redirect'). |
manufacturing (module gate) |
The manufacturing module must be enabled. | Constructor: with_module('manufacturing', 'redirect') — otherwise the user is redirected. |
manufacturing_labor_create |
Registered permission (“Create Labor Entry”). | Not separately enforced in this controller — all actions use manufacturing_labor. |
manufacturing_labor_update |
Registered permission (“Update Labor Entry”). | Not separately enforced in this controller. |
manufacturing_labor_delete |
Registered permission (“Delete Labor Entry”). | Not separately enforced in this controller. |
_create / _update /
_delete permissions exist in config but are not enforced here — so anyone with
manufacturing_labor can add, edit, and delete both labor and downtime records.
Grant it deliberately. Full matrix: Permissions & Roles.
Things to watch out for
datetime-local input, and elapsed
time is computed by simple string-to-time subtraction. Be consistent with the values you enter —
no time-zone conversion happens to the clock times.reported_by is set only on creation and never changed on edits;
resolved_by is re-set to the current user every time you save with an End Time
present.deleted = 1 after the standard
delete-confirmation dialog; records are hidden, not physically removed.production_loss_quantity column,
but it is not shown or editable in this feature and always defaults to 0.is_active = 1) to appear in
the downtime dropdown and filter; inactive centers won't be selectable. Manage them under
Work Centers.Where this connects
Labor & Downtime is a time-and-cost log — it does not move stock. It reads from and feeds these areas:
status=active,
user_type=staff); reporters and resolvers also reference user records. The
manufacturing REST/EU APIs expose downtime read endpoints (GET manufacturing/downtime
and manufacturing/downtime/{id}) plus the self-service clock in/out actions, all gated
by the same manufacturing_labor permission. There is no direct Inventory
integration — this feature deals with time and cost, not stock movements.