Quality Inspection Workflow
A design reference for gating stock acceptance on measured or judged quality readings
1. Requirements
1.1 Functional requirements
- Let a quality reviewer define, once, a reusable Inspection Template: a named list of parameters for a given item, each carrying its own acceptance rule (numeric range, exact value, or formula), bindable to an item or to a bill of materials.
- Record one Quality Inspection per item, per source transaction row, with a grid of Parameter Readings — one row per parameter, up to ten sample values, or a single value for non-numeric checks.
- Judge each reading against its rule automatically, roll the per-reading verdicts up into one overall Accepted/Rejected verdict, but still let a reviewer override either the row or the header verdict by hand.
- Support two acceptance-rule shapes per parameter: a numeric minimum/maximum band applied to every populated sample, or a free-form expression evaluated against the reading’s own values.
- Gate acceptance of stock at the point it enters or leaves a warehouse: block or warn on a receipt, delivery, or internal stock movement whose item requires inspection but has no linked, submitted, non-rejected Quality Inspection.
- Extend the same gate into manufacturing: block or warn on completion of a shop-floor operation whose bill of materials and operation both demand an in-process check on the finished good.
- Let a reviewer pick which specific line of a source document an inspection covers, so multiple rows of the same item on one document each carry their own inspection, and support bulk creation of one inspection per eligible line.
1.2 Non-functional requirements
- Configurability over hard-coding: whether a missing, unsubmitted, or rejected inspection blocks or merely warns is a company-wide setting, not a code branch per document type.
- Auditability: every reading, its rule, and the resulting verdicts are stored on a submittable record, not recomputed transiently.
- Safety of user-authored logic: the formula acceptance rule is operator-editable, so it must run inside a constrained evaluator rather than as an arbitrary script.
- Idempotent linkage: a source row can carry at most one active inspection at a time.
- Graceful degradation: a malformed formula fails with an actionable message rather than crashing the save.
1.3 Constraints
- Each Parameter Reading row has a fixed ten numeric sample slots, not a repeatable grid — sample size per parameter is capped at ten.
- The gate is evaluated synchronously inside the referencing document’s own save/submit cycle; there is no async or queued check.
- The gate’s mandatory/advisory behavior is a single company-wide switch pair, not configurable per item or per document type beyond the item-level flags themselves.
- A Quality Inspection always attaches to exactly one item on exactly one source row or shop-floor operation, never to a whole document.
2. High-Level Design
2.1 Component diagram
2.2 Authoring an Inspection Template (annotated procedure)
- A reviewer creates named Inspection Parameters (e.g. “Iron Content”), optionally tagged with a Parameter Group for categorization.
- The reviewer builds an Inspection Template, adding one row per parameter and fixing, per row: whether it is
numeric(sample-based) or single-value, and its acceptance rule (min/max band, exact value, or formula, chosen viaformula_based_criteria). - The template is linked to an item, or to a bill of materials, so its finished good inherits the same checklist.
- When a Quality Inspection is later opened for that item, its reading grid is copied in from the template, pre-loaded with each parameter’s rule and defaulted to Accepted until real readings are entered.
2.3 Recording an inspection and clearing the gate
Both rule shapes converge on the same gate — it only cares about the resulting verdict and submission state, not how the verdict was reached.
3. Deep Dive
3.1 Data model
Inspection Parameter — a master record naming one measurable or judgeable characteristic (“Iron Content”, “Color Match”), with a description and an optional link to a Parameter Group (a named category for filtering/reporting only, not evaluation).
Inspection Template — a named, reusable checklist. Its child table lists one row per Inspection Parameter, fixing numeric, formula_based_criteria, and the corresponding min_value/max_value, expected value, or acceptance_formula. An item, and separately a bill of materials, can each point at a template; opening a Quality Inspection with no readings yet copies the template’s rows in as the starting grid.
Quality Inspection — the submittable header. Key fields: inspection_type (Incoming / Outgoing / In Process), a polymorphic reference_type/reference_name pointing at the source transaction (a goods receipt, a purchase or sales invoice that itself posts stock, a delivery, a subcontracting receipt, an internal stock movement, or a shop-floor operation), item_code, optional batch_no/item_serial_no, sample_size, a template link, a header-level manual_inspection switch, and status (blank / Accepted / Rejected / Cancelled). A child_row_reference field disambiguates which specific source line this inspection covers, and a company field, populated from the source transaction at validation time, scopes it to the owning legal entity.
Parameter Reading — one child row per parameter, carrying the copied rule fields, up to ten sample fields reading_1…reading_10 (free text, since users enter numbers in locale-specific formats), a reading_value field used only when non-numeric, a row-level manual_inspection switch, and its own status.
3.2 Acceptance-criteria evaluation
Two independent rule shapes exist per row, selected by formula_based_criteria and numeric:
Exact match (non-formula, non-numeric). Accepted only if reading_value is exactly equal to the configured value — string equality, no tolerance.
Min/max band (non-formula, numeric). Every populated sample among reading_1…reading_10 must fall within min_value..max_value inclusive. Samples pass through a parser that swaps decimal comma/point per the site’s number-format setting before comparison. If no sample was entered at all, the row cannot be evaluated and is judged Rejected rather than silently passed.
Formula-based (either numeric or non-numeric). The row’s acceptance_formula — e.g. reading_1 > 0.2 and reading_1 < 0.5, mean > 3.5, or reading_value in ("A", "B", "C") — is evaluated through the platform’s constrained expression evaluator, not a bare interpreter call, with no custom global namespace and a small local-variable dictionary as its only data: for a numeric row, reading_1…reading_10 (missing samples default to 0.0) plus a precomputed mean; for non-numeric, just reading_value. The expression has no path to the parent document, sibling readings, or any database handle — the evaluator’s own deeper restrictions on callable language constructs live at the platform layer beneath this module and were not independently re-verified here, but this module’s contribution is that minimal, read-only data surface. Two failures are handled explicitly: a NameError (unknown field) is re-raised naming the offending field; any other exception becomes a generic “formula is incorrect” error. A row flagged formula-based with an empty formula fails validation before evaluation.
A row whose own manual_inspection switch is checked skips all of the above; its status is whatever the reviewer typed.
Header roll-up. Unless the header-level manual_inspection switch is checked, status is recomputed on every save: defaults to Accepted, flips to Rejected the moment any reading is Rejected (first match wins). If the header switch is checked, recomputation is skipped and the header status is whatever the reviewer set. Before submission, every reading must carry a non-blank status regardless of how it was reached.
One wrinkle: for in-process inspections tied to a shop-floor operation, reading rows are first refreshed from the item’s current template and reset to Accepted on every validation pass, then the roll-up above recomputes each row’s real verdict — a resync step, not a way to force acceptance.
3.3 Where the gate is enforced
Stock-transaction gate. A shared validation routine, inherited by every document type that posts a stock movement — receipts, purchase/sales invoices that post stock, deliveries, subcontracting receipts, and internal stock movements — runs on save and submit. For invoice- and receipt-style documents, whether a line needs inspection comes from two flags on the item’s own master (incoming/purchase-side, outgoing/delivery-side). For internal stock movements, a document-level “inspection required” switch gates the whole document, and which lines then need a link is decided by the movement’s declared purpose: a manufacturing output line, an allow-list of “incoming” purposes (receipt, repack, return-type receipts), and a separate allow-list of “outgoing” purposes (issue, transfer, send-to-subcontractor, disassembly); a purpose outside both lists needs none. Raw material consumed into a manufacturing run is deliberately excluded — the manufactured output is inspected, not each input.
Shop-floor gate. A separate check on the shop-floor operation enforces an in-process inspection only when both the governing bill of materials and that specific operation are flagged as requiring one — an AND, not an OR. If both are set and the operation carries no linked inspection, completing it is blocked outright with no advisory mode.
Shared configuration. A stock-module settings singleton supplies the policy both gates read: whether an unsubmitted link blocks or warns, and independently whether a Rejected link blocks or warns. A third switch — allow raising the inspection only after the receipt/delivery already went through — waives the presence/submission/rejection checks entirely for invoice- and receipt-style documents (not for stock movements or the shop-floor gate). Even the timing of when a source row’s link gets written differs by policy: written as soon as the inspection is saved as a draft in the default (advisory) mode, but only once the inspection is itself submitted in the strict (blocking) mode.
In every case the presence check is a hard requirement at submission time (it throws, not warns, once the document reaches docstatus 1) — “Warn” only softens the submission-state and rejection-state checks, not the base requirement that a flagged item be linked to an inspection at all.
3.4 Error handling and idempotency
- Missing inspection raises a distinct, named error at submit time, separate from “linked but not submitted” and “linked but rejected.”
- Bulk generation: one whitelisted call creates one inspection per eligible line, validating that the requested sample size does not exceed the accepted quantity on that line before saving.
- Duplicate/overlapping inspections: the item-lookup used when opening a new inspection only offers items whose row has no linked inspection yet, and a companion search filters existing inspections by item, source document, and line reference — keeping one active inspection per row without a database-level uniqueness constraint.
- Cancellation and cleanup: cancelling or discarding an inspection clears the link on the source row; discarding an unsubmitted draft additionally forces its own status to Cancelled. Cancelling a submitted inspection does not rewrite its Accepted/Rejected status — only the link clears, and the source row reopens as uninspected.
3.5 Status lifecycle
Two subtleties: an inspection can be submitted while Rejected — the verdict and the submission gate are independent, and it is the referencing document’s own gate that decides whether a Rejected-but-submitted inspection blocks or warns; and the “Cancelled” status is reached only by discarding a still-unsubmitted draft — cancelling an already-submitted inspection changes its submission state but leaves its verdict untouched.
4. Scale and Reliability
- Synchronous, per-document cost: the gate runs inline inside the referencing document’s own save/submit transaction with no batching or async offload, so cost scales with the number of inspectable lines on that document.
- Fixed sample width: ten fixed sample fields per parameter row, not a repeatable grid, so plans needing more than ten measurements must split across multiple parameter rows, splitting the acceptance rule too.
- Bulk creation is a simple loop: one inspection per eligible line, iterated item-by-item with no batching or per-item error isolation — a candidate for chunking if ever exposed as a background job.
- No caching, no concurrency guard: presence/submission/rejection are re-derived from live lookups on every validate/submit call (immediate correctness, a round trip per flagged line per save), and the one-active-inspection-per-row invariant is enforced by scoping which items the UI offers rather than a database constraint.
5. Trade-off Analysis
| Decision | Trade-off |
|---|---|
| Two acceptance-rule shapes (numeric band vs. formula) on one parameter row | Covers common and irregular criteria without a second record type, at the cost of a formula field that is harder to audit than a bounded numeric pair. |
| Formula run through a constrained evaluator with a minimal local-variable surface | Lets quality staff author logic without a code deployment, and the expression’s visible world is limited to that row’s own values — safer than a bare eval, though the evaluator’s deeper restrictions live outside this module and were not independently verified here. |
| Presence check is a hard stop at submit regardless of the warn/stop settings | Prevents the settings from being misread as “inspection is optional”; more rigid than one unified switch, but avoids a document submitting with no inspection at all. |
| Shop-floor gate requires BOTH the bill of materials and the operation to demand inspection | Avoids over-triggering on every operation of an inspected product, but a bill-of-materials-level intent is inert unless the operation is also flagged. |
| Fixed ten-slot sample grid per parameter | Keeps the reading table simple, but hard-caps sample size and forces splitting one parameter into several rows for larger plans. |
| One-inspection-per-row invariant enforced by UI-side filtering, not a database constraint | Cheap and sufficient while inspections are created through standard paths; would not catch a directly-inserted duplicate. |
| Independent header-level and row-level manual-override switches | Fine-grained control without giving up automatic computation by default, at the cost of two override flags instead of one. |
6. What to Revisit as the System Grows
- Sample-width ceiling: fixed
reading_1…reading_10fields cap sample size at ten; a repeatable sample sub-table would remove the cap at the cost of a more complex evaluation loop. - Silent numeric coercion: sample values are free text run through a locale-aware float parser; unparsable input is coerced to zero rather than rejected, so a
mean-based formula can quietly absorb a data-entry error instead of surfacing it. - Formula sandbox not independently verified: this module hands the formula to the platform’s constrained evaluator with a minimal data surface, but its deeper restrictions (which operators or attributes are reachable) live outside this module’s source and were not confirmed here — worth an explicit, independently-reviewed allow-list given these formulas are authored by quality staff, not developers.
- “Cancelled” status is discard-only: cancelling a submitted inspection never sets
statusto Cancelled, so status-based reporting cannot distinguish a voided submitted inspection from an ordinary Accepted/Rejected one. - No database-level uniqueness for one-inspection-per-row: as integrations multiply, relying solely on UI-side filtering becomes fragile.
- Quantity/valuation effects of the transactions this gate protects are handled by the stock-ledger module’s own posting funnel, described separately; this document stops at “is the item allowed to be accepted.” Process-level quality concerns — formal procedures, scheduled reviews, corrective/preventive action loops — are likewise covered by the process-level quality management design described separately.