By Isaac Muthui, CEO, Tamalaki Business Network Ltd. Originally published at factoraerp.com.

In most factory software, cleaning chemicals and lubricants are an afterthought. They sit in the inventory as "consumables", next to gloves and printer paper, with a stock level and a reorder point. That is enough to buy them. It is not enough to answer the questions a food safety auditor, a safety officer or an incident investigation will ask. Is this sanitiser approved for food-contact surfaces? Where is its safety data sheet? Which grease went on the filler last week, and was it food grade? Who mixed the bucket that was found unlabelled next to the line?

This post walks through how we think about modelling chemical control 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 read like a spec:

  • Codex CXC 1-1969, 13.2.6: "Systems should be in place to prevent or minimize contamination of foods by harmful chemicals, e.g. cleaning materials, non-food grade lubricants, chemical residues from pesticides..." Toxic cleaning compounds, disinfectants and pesticides "should be identified, safely stored and used in a manner that protects against contamination". Chemicals should be used with "the correct dilutions and contact times" and stored "in clearly identified containers". Source
  • 21 CFR 117.35(b): only toxic materials needed for cleaning and sanitation, laboratory testing, plant and equipment maintenance and operation, or the plant's operations may be used or stored where food is processed or exposed. Source
  • 21 CFR 178.3570: lubricants with incidental food contact must be made from listed substances, and the amount used must be "the minimum required to accomplish the desired technical effect". Source
  • Kenya, Occupational Safety and Health Act 2007, s.84-85: safety data sheets available at the workplace for all chemicals in use; all chemicals labelled or marked; and a "record of hazardous chemicals used at the workplace, cross-referenced to the appropriate chemical safety data sheets". Source
  • EAS 39:2001, 7.3.13: pesticides "shall not be used while processing is in progress". Source

Translated: every chemical needs an identity, an approval and a safety data sheet; it has an allowed purpose; it lives in a known, segregated location; it is often transformed (diluted, decanted) into new containers that still need identity; some equipment points demand a specific grade; some activities are forbidden while production runs; and there must be a queryable record of all of it.

The core entities

Chemical (id, name, supplier_id, category, purpose_class, food_contact_ok, food_grade_lubricant, sds_current_id, status) SafetyDataSheet (id, chemical_id, revision, issued_on, file_ref) ChemicalApproval (chemical_id, approved_by_qa, approved_by_safety, approved_at) StorageLocation (id, name, segregation_group, is_food_area) ChemicalLot (id, chemical_id, location_id, qty, unit, received_on, expires_on) WorkingSolution (id, parent_lot_id, container_label, target_conc, measured_conc, made_by, made_at, discard_after) LubricationPoint (id, equipment_id, description, food_contact_risk) ChemicalUse (id, kind, chemical_lot_id, solution_id, point_id, area_id, qty, used_by, used_at, production_running) 

A few design choices matter more than the table names.

1. "Purpose class" is an allow-list, not a tag

The US rule's four categories make a neat enum: cleaning, laboratory, maintenance, operations. A chemical that cannot be assigned one should not be approvable. Approval itself needs two signatures, QA for food safety and the safety officer for worker safety, and a current SDS:

ALTER TABLE chemical ADD CONSTRAINT approved_needs_sds CHECK ( status <> 'approved' OR sds_current_id IS NOT NULL ); 

The OSH Act's "record cross-referenced to the data sheets" then falls out as a plain join.

2. Dilution creates a new identity

The unlabelled bucket is the classic failure, and it happens because software tracks the drum but not what is made from it. Treat a working solution like a child lot: it has a parent lot, a label, a target and measured concentration, who made it, and when it must be discarded. Printing the label is part of creating the record, so there is no record without a label and no labelled bucket without a record. When a QA walk finds a container, its label should resolve to a row.

3. Lubrication points carry the rule, not the grease

Whether a lubricant must be food grade depends on where it goes. Model the point, not just the product:

-- block non-food-grade lubricant on a food-contact-risk point CREATE FUNCTION check_lube_grade() RETURNS trigger AS $$ BEGIN IF NEW.kind = 'lubrication' AND EXISTS ( SELECT 1 FROM lubrication_point p, chemical_lot l, chemical c WHERE p.id = NEW.point_id AND l.id = NEW.chemical_lot_id AND c.id = l.chemical_id AND p.food_contact_risk AND NOT c.food_grade_lubricant ) THEN RAISE EXCEPTION 'Point requires a food-grade lubricant'; END IF; RETURN NEW; END $$ LANGUAGE plpgsql; 

This pairs naturally with the preventive maintenance schedule: the work order names the point, so the technician picks from an already-filtered list.

4. Some uses are time-boxed by production state

EAS 39 forbids pesticide use while processing is in progress. If the system knows which lines are running (it does, if production orders are in the same database), a pest treatment record can be checked against the production calendar for that area. Flag, rather than block, because treatments are often recorded after the fact by a contractor; a flagged record is an audit finding, not a silent gap.

5. Usage reconciliation is a quality signal

Stock movements for chemicals are not just costing. Expected usage can be estimated from cleaning schedules and production hours; actual usage comes from store issues. A large variance either way is worth a look: overuse can mean over-strong dilution and residue risk, underuse can mean surfaces that were not properly sanitised. A weekly variance report per area is a cheap early warning.

What we keep simple

Small factories will not maintain a perfect chemical master. The model has to survive that:

  • Default every lubrication point on food equipment to food_contact_risk = true and make people justify switching it off.
  • Let the SDS be a scanned file with a revision date; parsing it is not required to get value.
  • Make the "unlabelled container found" event one tap on a phone, linked to an area and a photo, feeding the same non-conformance workflow as audit findings.

Where FactoraERP is today

In FactoraERP, chemicals already live in the inventory catalogue under their own category with supplier, location, stock, reorder level and receipt and issue movements; safety incidents (including near misses and hazards) and quality non-conformances have their own records. The dilution, lubrication-point and approval layers above are the direction we are designing towards, and we would welcome views from anyone who has modelled this in another domain.

How do you model "derived" containers in your systems, the bucket made from the drum, so that identity survives the transformation?