Maintenance for palm oil mills. Every machine keeps its own chart — what has gone wrong with this one, how often, and who fixed it.
ONE MACHINE'S RECORD
A mill is a hundred machines that all run at once. Every one will break. That was never the question. What costs you is what comes after: what broke last time, and what did we do about it?
Before this, that answer sat in a logbook, a WhatsApp thread, or somebody's memory. None of it searchable.
My first attempt recorded work: log the fault, assign it, close it. A ticketing system with a maintenance vocabulary. That was wrong, and it took a while to see why.
Recording work tells you what your people did. It tells you nothing about your machines — and the machine is what stops production.
Every machine should have a chart. You open it and see what has gone wrong with this one, how often, and what was done — like a doctor pulling a patient's history before deciding anything.
Once you have that, something useful falls out for free. A screw press that used to fail every 38 days starts failing every 20. No single work order looks alarming. But the chart says it plainly, months before the machine strands you mid-season.
That is competency, not preference. So when a fault is tagged electrical, only certified chargemen appear in the assignment list. The mistake stops being possible rather than being caught afterwards.
We work in pairs for safety. A job can be assigned to two, and when it is assigned to one, that shows. Not to police anybody — but if you are recording who did the work anyway, you may as well be able to answer whether the buddy system was followed.
A technician submits; someone else verifies. That single separation is what makes the history worth trusting a year later.
The alternative is a system half the crew quietly works around.
None of that is a screen validation. It sits in the database rules, where the app cannot talk its way past it — and neither can I.
function was() { return resource.data.status; }
function now_() { return request.resource.data.status; }
function mine_() { return request.auth.uid in resource.data.assignees; }
function mayAssign() {
return myRole() == 'admin'
|| (myRole() == 'foreman' && me().trade == resource.data.faultType);
}
function mayVerify() {
return myRole() == 'admin' || myRole() == 'foreman' || myRole() == 'reporter';
}
function historySealed() {
return frozen('code') && frozen('reportedBy') && frozen('reportedAt')
&& frozen('assetId') && frozen('shift') && frozen('faultType');
}
allow update: if activeUser()
&& historySealed()
&& tenancyFrozen()
&& frozen('downtimeMinutes') && frozen('soloJob')
&& (
(mayAssign()
&& was() in ['REPORTED', 'ASSIGNED', 'IN_PROGRESS']
&& now_() in ['ASSIGNED', 'REPORTED']
&& request.resource.data.assignees.size() ==
request.resource.data.assigneeNames.size()
&& (now_() == 'REPORTED' || selfStamp('assignedBy')))
|| (myRole() == 'technician' && mine_()
&& was() in ['ASSIGNED', 'IN_PROGRESS']
&& now_() in ['IN_PROGRESS', 'PENDING_VERIFICATION']
&& frozen('assignees')
&& (now_() == 'IN_PROGRESS'
|| (selfStamp('closedBy')
&& request.resource.data.closedAt == request.time)))
|| (mayVerify() && was() == 'PENDING_VERIFICATION' && now_() == 'CLOSED'
&& selfStamp('verifiedBy')
&& request.resource.data.verifiedAt == request.time)
|| (myRole() == 'admin' && now_() == 'CANCELLED'
&& was() != 'CLOSED' && was() != 'CANCELLED'
&& selfStamp('cancelledBy')
&& request.resource.data.cancelReason is string
&& request.resource.data.cancelReason.size() > 0)
);
THE WORK ORDER STATE MACHINE, AS ENFORCED
Live at one Malaysian mill. No app store, no hardware, no IT project — it runs on the phones the crew already carry, and everyone signs in once.
If you run a mill and this sounds like your problem — akmalkharolanuar@gmail.com