By Isaac Muthui, CEO, Tamalaki Business Network Ltd. Originally published at factoraerp.com.
Most factory systems store a food handler's medical certificate as an attachment on an employee record: a scanned PDF, maybe an expiry date. That answers "do we have a certificate on file?". It does not answer the question an auditor, an inspector or your own QA lead asks after a problem: was every person who handled this batch fit to handle food at the time?
"Fit to handle food" is not a document. It is a state that changes during the week. A certificate expires. Someone reports diarrhoea on Tuesday and is moved off the line. A casual packer from a labour contractor starts on Wednesday. A fitter walks in from the workshop. This post walks through how we think about modelling that in an ERP for food and agro manufacturers in East Africa. The operational checklist behind it is in the original article.
What the domain rules say
The requirements come from food safety rules, not from software people:
- Kenya's Food Hygiene Regulations, 1978 (regulation 15) require a medical examination for all employees before employment and "at regular intervals of not more than twelve months", with each employee's health certificate and health records kept at the plant, and no one with a communicable disease or infected wounds working where food could be contaminated. Source
- EAS 39:2001 (5.2) says no one works as a food handler until certified fit, asks for a new certificate after a water- or food-borne illness or "an absence of more than three days", keeps staff with gastrointestinal illness away from exposed food, and asks for training records and records showing staff were screened and trained (5.3, 14.3.3). Source
- Codex CXC 1-1969 (12.1, 12.2) says ill people should report symptoms to management immediately, lists the symptoms (jaundice, diarrhoea, vomiting, fever, sore throat with fever, infected skin lesions, discharges from ear, eye or nose), and allows for exclusion until a set time after symptoms resolve or medical clearance. Source
Read as a spec: eligibility depends on a valid certificate, the absence of an open illness exclusion, a return-to-work condition after illness or long absence, and current training. All of it is time-bound.
Model the inputs as dated facts
Person
id, type -- employee | casual | agency | contractor | visitor
supplier_id -- labour contractor, if any
MedicalCertificate
id, person_id, issuer, certificate_no
examined_on, valid_until
verified_by, verified_at -- someone checked it came from the authorised issuer
IllnessReport
id, person_id, reported_at, symptoms[]
action -- reassign | send_home | refer
cleared_at, clearance_type -- time_based | medical_certificate
TrainingRecord
id, person_id, course -- induction | refresher
completed_on, valid_until
EntryCheck
id, person_id, shift_id, area_id, checked_at, checked_by
fitness_declared -- "any symptoms?" answered no
hands, clothing, jewellery, dressings -- pass | fail
result -- admitted | refused | reassigned
Nothing here is exotic. The useful part is that every record has a time, so eligibility can be computed for any moment rather than stored as a flag that someone forgets to update.
Derive eligibility, do not store it
A person is eligible to handle exposed food at time t when all of these hold:
- A verified
MedicalCertificateexists withexamined_on <= t <= valid_until. - No
IllnessReportis open att(reported, not yet cleared). - If an illness report or an absence of more than three days ended before
t, a certificate dated after it exists (the EAS 39 return-to-work rule). Absences come from the attendance data you already have. - A current induction
TrainingRecordexists. - An
EntryCheckfor that shift and area hasresult = admitted.
As a function this is boring, which is the point. It can run at the gate (should this person be admitted?), in planning (who can I roster on the filler tomorrow?), and in reverse after the fact.
The reverse query is the one auditors ask
Production output already carries line, shift and time. If shift assignments link people to lines, the audit question becomes a join:
SELECT o.lot_id, a.person_id
FROM production_output o
JOIN shift_assignment a
ON a.line_id = o.line_id
AND a.started_at < o.ended_at
AND a.ended_at > o.started_at
WHERE o.lot_id = :lot
AND NOT is_eligible(a.person_id, o.started_at);
An empty result is your evidence. A non-empty one is a finding: open a non-conformance, link the lots, and let QA decide whether they go on hold. The same query run forward each morning (who is rostered but will not be eligible?) prevents most findings from happening at all.
Contractors and visitors are people too
The common gap is that casual staff, agency workers and contractors live outside the HR system, so their certificates and training are never checked. Putting them in the same Person table, with a supplier_id for the labour contractor, means the same eligibility rule applies and expiring certificates can be flagged to the contractor before they lapse. Visitors get a lighter record: a declaration of symptoms and wounds, agreement to the clothing and jewellery rules, and the areas they entered.
What to keep simple
- The entry check should be a phone form a supervisor completes in seconds, with a server timestamp. If it takes a minute per person it will be skipped.
- Keep the symptom list and the exclusion rules as configuration, because they come from your food safety plan and your county public health office, not from code.
- Do not store medical details you do not need. For eligibility you need dates, the issuer and an outcome, not a diagnosis.
The general lesson, again: in food manufacturing the useful record is not "the document exists" but "the person was in a known good state over this interval". Once fitness, illness, training and entry checks are dated facts tied to people and shifts, "who handled this batch, and were they fit?" becomes a query.
The full 8-step personal hygiene and staff health checklist, with the Codex, EAS 39 and Kenyan regulation references, is on the FactoraERP blog.
Isaac Muthui, CEO, Tamalaki Business Network Ltd









