GuideMarch 13, 2026

Quality Control in Odoo 19:
Inspections, Alerts & Automated Quarantine

INTRODUCTION

Your Warehouse Ships 500 Orders a Day. How Many Get Inspected?

Most Odoo implementations treat quality control as an afterthought. Inventory is configured, manufacturing routes are set up, and somewhere in month three someone asks: "Wait, how do we reject a batch that fails inspection?" By then, the warehouse team has already built a parallel process in spreadsheets — tracking defects in Google Sheets, quarantining stock by physically moving pallets to a corner, and emailing the purchasing team when a vendor ships bad product.

Odoo 19's Quality module (quality_control, quality_mrp, quality_stock) eliminates this shadow process. It lets you define quality control points that automatically trigger inspections at specific stages — incoming receipt, manufacturing work order, outbound picking. Each inspection can be a simple pass/fail, a dimensional measurement with tolerances, or a photo capture for visual verification. When an inspection fails, the system can automatically quarantine stock, raise alerts, and notify responsible teams — no spreadsheets, no manual moves, no forgotten follow-ups.

This guide walks through a complete quality control setup in Odoo 19: from enabling the module and defining control points, through configuring each inspection type, to building automated quarantine workflows that move failed stock to a dedicated location without human intervention.

01

Setting Up Quality Control Points in Odoo 19: Where, When, and What to Inspect

A quality control point (QCP) is a rule that tells Odoo: "Every time operation X happens for product Y, create an inspection." QCPs are the backbone of the entire quality system. Get them right and inspections happen automatically. Get them wrong and your team either inspects nothing or drowns in unnecessary checks.

Step 1: Enable the Quality Module

Navigate to Settings → General Settings → Inventory and enable Quality. This installs quality_control and its dependencies. If you use Manufacturing, also install quality_mrp for work order integration.

Step 2: Define a Quality Control Point

Go to Quality → Quality Control → Control Points and create a new record. Here is a Python example showing how to programmatically create QCPs for incoming inspections:

Python — models/quality_control_point.py
from odoo import models, fields, api


class QualityControlPointSetup(models.TransientModel):
    """Wizard to bulk-create quality control points for
    all products in a given category."""
    _name = 'quality.control.point.setup'
    _description = 'Bulk QCP Setup Wizard'

    product_category_id = fields.Many2one(
        'product.category', string='Product Category', required=True,
    )
    picking_type_id = fields.Many2one(
        'stock.picking.type', string='Operation Type', required=True,
        help='e.g., Receipts, Delivery Orders, Internal Transfers',
    )
    test_type_id = fields.Many2one(
        'quality.point.test_type', string='Inspection Type', required=True,
    )
    team_id = fields.Many2one(
        'quality.alert.team', string='Quality Team',
    )
    note = fields.Html(string='Instructions')

    def action_create_control_points(self):
        """Create one QCP per product in the selected category."""
        QCP = self.env['quality.point']
        products = self.env['product.product'].search([
            ('categ_id', 'child_of', self.product_category_id.id),
            ('type', '=', 'product'),  # Only storable products
        ])
        vals_list = []
        for product in products:
            vals_list.append({
                'name': f'Incoming QC - {product.name}',
                'product_ids': [(4, product.id)],
                'picking_type_ids': [(4, self.picking_type_id.id)],
                'test_type_id': self.test_type_id.id,
                'team_id': self.team_id.id if self.team_id else False,
                'note': self.note or '',
                'company_id': self.env.company.id,
            })
        created = QCP.create(vals_list)
        return {
            'type': 'ir.actions.client',
            'tag': 'display_notification',
            'params': {
                'title': 'Quality Control Points Created',
                'message': f'{len(created)} control points created.',
                'type': 'success',
            },
        }

Step 3: Configure the QCP Form

Each QCP has these critical fields. Getting the combination right determines whether your inspections are useful or noise:

FieldWhat It ControlsRecommended Setting
ProductWhich product(s) trigger this inspectionSet specific products for high-risk items; leave blank for category-wide rules
Operation TypeWhich warehouse operation triggers the checkReceipts for incoming QC, Manufacturing for in-process QC
Control perOne check per operation, per product, or per lotPer lot for traceability; per operation for general screening
TypePass/Fail, Measure, Take a Picture, or customPass/Fail for visual checks; Measure for dimensional tolerances
Quality TeamWho gets notified and who is responsibleAssign a dedicated QC team, not the generic warehouse team
InstructionsWhat the inspector sees on the check formInclude photos of acceptable vs. defective. Be specific.
Control Per Lot vs. Per Operation

"Control per operation" creates one check per transfer — a single receipt with 10 products generates one check. "Control per product" creates one check per product line — that same receipt generates 10 checks. "Control per lot" creates one check per lot/serial number. For regulated industries (food, pharma, aerospace), always use per-lot. For general merchandise, per-product is the right balance between coverage and overhead.

02

Odoo 19 Inspection Types: Pass/Fail, Measure, and Picture-Based Quality Checks

Odoo 19 ships with three built-in inspection types. Each serves a different purpose, and choosing the wrong one creates friction for inspectors:

Pass/Fail Inspections

The simplest type. The inspector sees the product, reads the instructions, and clicks Pass or Fail. Use this for visual inspections: "Does the packaging look damaged?", "Is the label printed correctly?", "Are all items present in the kit?"

Measure Inspections

The inspector enters a numeric value. The QCP defines a norm (target) and tolerance (acceptable range). Odoo automatically marks the check as passed or failed based on whether the measurement falls within tolerance. This is essential for dimensional quality: "Measure the shaft diameter — norm 25.0mm, tolerance +/- 0.1mm."

Python — models/quality_check_measure.py
from odoo import models, fields, api
from odoo.exceptions import ValidationError


class QualityCheckMeasureExtension(models.Model):
    """Extend quality checks to support multi-point measurements
    and statistical process control (SPC) tracking."""
    _inherit = 'quality.check'

    measure_values = fields.Text(
        string='Individual Measurements',
        help='Comma-separated measurements for SPC analysis',
    )
    cpk_index = fields.Float(
        string='Cpk Index', digits=(4, 3), compute='_compute_cpk',
        store=True,
    )

    @api.depends('measure_values', 'point_id.norm',
                 'point_id.tolerance_min', 'point_id.tolerance_max')
    def _compute_cpk(self):
        """Calculate process capability index from measurements."""
        import statistics
        for check in self:
            if not check.measure_values or not check.point_id:
                check.cpk_index = 0.0
                continue
            try:
                values = [
                    float(v.strip())
                    for v in check.measure_values.split(',')
                    if v.strip()
                ]
            except ValueError:
                check.cpk_index = 0.0
                continue
            if len(values) < 2:
                check.cpk_index = 0.0
                continue

            mean = statistics.mean(values)
            stdev = statistics.stdev(values)
            if stdev == 0:
                check.cpk_index = 99.99  # Perfect process
                continue

            usl = check.point_id.tolerance_max
            lsl = check.point_id.tolerance_min
            cpu = (usl - mean) / (3 * stdev)
            cpl = (mean - lsl) / (3 * stdev)
            check.cpk_index = min(cpu, cpl)

    def action_validate_measurement(self):
        """Validate measurement against tolerances and
        auto-fail if out of spec."""
        self.ensure_one()
        if not self.measure:
            raise ValidationError('Enter a measurement before validating.')
        norm = self.point_id.norm
        tol_min = self.point_id.tolerance_min
        tol_max = self.point_id.tolerance_max
        if tol_min <= self.measure <= tol_max:
            self.do_pass()
        else:
            self.do_fail()

Picture Inspections

The inspector takes a photo directly from the check form (works on mobile and tablet). The image is stored on the check record for audit purposes. Use this for surface defects, label verification, or any inspection where visual evidence is required for compliance.

Registering Custom Inspection Types

Odoo 19 lets you register additional test types beyond the three defaults. Here is how to add a "Barcode Scan" inspection type that verifies the scanned barcode matches the expected product:

Python — models/quality_point_test_type.py
from odoo import models, fields


class QualityPointTestType(models.Model):
    _inherit = 'quality.point.test_type'

    # Odoo 19 uses a selection-style technical_name
    # to drive the check form rendering
    technical_name = fields.Char()


# In your module's data file, register the new type:
# (see XML below)
XML — data/quality_point_test_type_data.xml
<odoo>
  <data noupdate="1">
    <record id="test_type_barcode" model="quality.point.test_type">
      <field name="name">Barcode Scan</field>
      <field name="technical_name">barcode_scan</field>
    </record>
  </data>
</odoo>
Mobile-First Inspections

Warehouse inspectors rarely sit at a desk. Odoo 19's quality check forms are fully responsive and work on mobile browsers. The picture inspection type uses the device camera directly. For measure inspections, consider connecting Bluetooth calipers or scales via the IoT Box — the measurement auto-populates the field without manual entry, eliminating transcription errors.

03

Quality Alerts in Odoo 19: Automated Escalation When Inspections Fail

When an inspection fails, Odoo creates a quality alert. Alerts are Odoo's mechanism for tracking defects from discovery through root cause analysis to corrective action. Think of them as "defect tickets" — they have a responsible team, a pipeline stage (New → Confirmed → In Progress → Done), and full chatter history.

Automatic Alert Creation on Failure

By default, when an inspector clicks Fail on a quality check, Odoo creates an alert linked to the check, the product, the lot (if applicable), and the source operation. But the default behavior stops there — no email, no assignment, no escalation. Here is how to add automated notification logic:

Python — models/quality_alert.py
from odoo import models, fields, api
import logging

_logger = logging.getLogger(__name__)


class QualityAlertAutomation(models.Model):
    _inherit = 'quality.alert'

    severity = fields.Selection([
        ('low', 'Low — Cosmetic'),
        ('medium', 'Medium — Functional'),
        ('high', 'High — Safety'),
        ('critical', 'Critical — Recall Risk'),
    ], default='medium', required=True, tracking=True)
    vendor_id = fields.Many2one(
        'res.partner', string='Vendor',
        compute='_compute_vendor', store=True,
    )
    total_affected_qty = fields.Float(
        string='Affected Quantity',
        help='Total units in the defective lot/batch',
    )

    @api.depends('lot_id')
    def _compute_vendor(self):
        """Auto-fill vendor from the lot's original receipt."""
        for alert in self:
            if alert.lot_id:
                # Find the original receipt move for this lot
                move = self.env['stock.move.line'].search([
                    ('lot_id', '=', alert.lot_id.id),
                    ('picking_id.picking_type_code', '=', 'incoming'),
                ], limit=1, order='date asc')
                alert.vendor_id = (
                    move.picking_id.partner_id if move else False
                )
            else:
                alert.vendor_id = False

    @api.model_create_multi
    def create(self, vals_list):
        """Override create to send notifications based on severity."""
        alerts = super().create(vals_list)
        for alert in alerts:
            self._send_severity_notification(alert)
        return alerts

    def _send_severity_notification(self, alert):
        """Route notifications based on alert severity."""
        if alert.severity in ('high', 'critical'):
            # Notify quality manager immediately
            template = self.env.ref(
                'quality_custom.mail_template_critical_alert',
                raise_if_not_found=False,
            )
            if template and alert.team_id.alias_user_id:
                template.send_mail(
                    alert.id, force_send=True,
                    email_values={
                        'email_to': alert.team_id.alias_user_id.email,
                    },
                )
            _logger.warning(
                'CRITICAL quality alert #%s for product %s, lot %s',
                alert.name, alert.product_id.display_name,
                alert.lot_id.name if alert.lot_id else 'N/A',
            )
        if alert.severity == 'critical' and alert.vendor_id:
            # Auto-create vendor complaint
            alert.message_post(
                body=f'Critical defect detected. '
                     f'Vendor: {alert.vendor_id.name}. '
                     f'Initiating vendor complaint workflow.',
                message_type='notification',
            )

Alert Pipeline Configuration

Configure your quality alert stages to match your corrective action process. A proven pipeline for manufacturing environments:

StageWho Owns ItExpected ActionSLA
NewQuality InspectorDocument defect, attach photos, assign severityImmediate
ConfirmedQuality Team LeadVerify defect, confirm severity, initiate quarantine4 hours
Root CauseQuality Engineer5-Why analysis, identify corrective action48 hours
Corrective ActionResponsible DepartmentImplement fix, update process, retrain if needed1 week
DoneQuality ManagerVerify fix effectiveness, close alertReview in 30 days
Link Alerts to Vendor Scorecards

Every quality alert with a vendor should feed into your vendor evaluation. Odoo 19's Purchase module has a built-in vendor rating system. Use the vendor_id computed field above to automatically track defect rates per supplier. When a vendor's defect rate exceeds your threshold (say 5%), trigger a review workflow. This turns reactive quality management into proactive supply chain improvement.

04

Automated Quarantine Workflows: Moving Failed Stock Without Human Intervention

Quarantine is where most Odoo quality setups break down. The inspection fails, an alert is created, but the stock stays in the main warehouse location. An operator can still pick it for a customer order. Someone has to manually create an internal transfer to move the defective product to a quarantine location. This manual step gets forgotten 30% of the time.

The solution: automate the quarantine move. When a quality check fails, Odoo should immediately create and validate an internal transfer moving the affected quantity to a dedicated quarantine location.

Step 1: Create the Quarantine Location

Go to Inventory → Configuration → Warehouses, select your warehouse, and under Locations, create a child location called WH/Quarantine. Mark it as an internal location. Optionally, set a removal strategy of FEFO so quarantined stock with expiration dates gets reviewed first.

XML — data/stock_location_data.xml
<odoo>
  <data noupdate="1">
    <!-- Quarantine location under main warehouse -->
    <record id="stock_location_quarantine" model="stock.location">
      <field name="name">Quarantine</field>
      <field name="usage">internal</field>
      <field name="location_id" ref="stock.warehouse0" />
      <field name="scrap_location">False</field>
      <field name="company_id" ref="base.main_company" />
    </record>

    <!-- Internal transfer type for quarantine moves -->
    <record id="picking_type_quarantine" model="stock.picking.type">
      <field name="name">Quarantine Transfers</field>
      <field name="code">internal</field>
      <field name="sequence_code">QRN</field>
      <field name="default_location_src_id" ref="stock.stock_location_stock" />
      <field name="default_location_dest_id" ref="stock_location_quarantine" />
      <field name="warehouse_id" ref="stock.warehouse0" />
      <field name="company_id" ref="base.main_company" />
    </record>
  </data>
</odoo>

Step 2: Auto-Quarantine on Inspection Failure

This is the core automation. Override the do_fail method on quality.check to automatically create and validate an internal transfer:

Python — models/quality_check_quarantine.py
from odoo import models, fields, api, _
from odoo.exceptions import UserError
import logging

_logger = logging.getLogger(__name__)


class QualityCheckQuarantine(models.Model):
    _inherit = 'quality.check'

    quarantine_picking_id = fields.Many2one(
        'stock.picking', string='Quarantine Transfer',
        readonly=True, copy=False,
    )

    def do_fail(self):
        """Override to auto-quarantine stock on failure."""
        res = super().do_fail()
        for check in self:
            if check.picking_id and check.product_id:
                check._create_quarantine_transfer()
        return res

    def _create_quarantine_transfer(self):
        """Create an internal transfer to move failed stock
        from its current location to quarantine."""
        self.ensure_one()
        quarantine_loc = self.env.ref(
            'quality_custom.stock_location_quarantine',
            raise_if_not_found=False,
        )
        if not quarantine_loc:
            _logger.error(
                'Quarantine location not found. '
                'Cannot auto-quarantine for check %s.', self.name,
            )
            return

        picking_type = self.env.ref(
            'quality_custom.picking_type_quarantine',
            raise_if_not_found=False,
        )
        if not picking_type:
            return

        # Determine source location and quantity
        source_loc = self.picking_id.location_dest_id
        failed_qty = self._get_failed_quantity()

        if failed_qty <= 0:
            return

        picking_vals = {
            'picking_type_id': picking_type.id,
            'location_id': source_loc.id,
            'location_dest_id': quarantine_loc.id,
            'origin': _('QC Fail: %s', self.name),
            'move_ids': [(0, 0, {
                'name': _('Quarantine: %s', self.product_id.display_name),
                'product_id': self.product_id.id,
                'product_uom_qty': failed_qty,
                'product_uom': self.product_id.uom_id.id,
                'location_id': source_loc.id,
                'location_dest_id': quarantine_loc.id,
                'lot_ids': (
                    [(4, self.lot_id.id)] if self.lot_id else []
                ),
            })],
        }
        picking = self.env['stock.picking'].create(picking_vals)
        picking.action_confirm()
        picking.action_assign()

        # Auto-validate if stock is available
        for move_line in picking.move_line_ids:
            move_line.quantity = move_line.quantity_product_uom
        picking.button_validate()

        self.quarantine_picking_id = picking.id
        self.message_post(
            body=_(
                'Auto-quarantine: %(qty)s %(uom)s of %(product)s '
                'moved to %(loc)s (Transfer: %(ref)s)',
                qty=failed_qty,
                uom=self.product_id.uom_id.name,
                product=self.product_id.display_name,
                loc=quarantine_loc.complete_name,
                ref=picking.name,
            ),
            message_type='notification',
        )
        _logger.info(
            'Auto-quarantine transfer %s created for check %s',
            picking.name, self.name,
        )

    def _get_failed_quantity(self):
        """Determine the quantity to quarantine.
        For lot-controlled products, quarantine the full lot qty.
        For non-lot products, quarantine the received qty."""
        self.ensure_one()
        if self.lot_id:
            # Quarantine the entire lot
            quant = self.env['stock.quant'].search([
                ('lot_id', '=', self.lot_id.id),
                ('location_id', '=',
                 self.picking_id.location_dest_id.id),
                ('quantity', '>', 0),
            ], limit=1)
            return quant.quantity if quant else 0.0

        # No lot: quarantine the qty from the source move
        move_line = self.picking_id.move_line_ids.filtered(
            lambda ml: ml.product_id == self.product_id
        )
        return sum(move_line.mapped('quantity'))

Step 3: Add a View for the Quarantine Link

Extend the quality check form so inspectors can see and navigate to the quarantine transfer:

XML — views/quality_check_views.xml
<odoo>
  <record id="quality_check_view_form_inherit_quarantine"
          model="ir.ui.view">
    <field name="name">quality.check.form.quarantine</field>
    <field name="model">quality.check</field>
    <field name="inherit_id"
           ref="quality_control.quality_check_view_form" />
    <field name="arch" type="xml">
      <xpath expr="//field[@name='team_id']" position="after">
        <field name="quarantine_picking_id"
               readonly="1"
               invisible="not quarantine_picking_id"
               widget="many2one"
               options="{{'no_create': True}}" />
      </xpath>
    </field>
  </record>
</odoo>
Don't Use Scrap Location for Quarantine

A common mistake is using Odoo's built-in Virtual/Scrap location for quarantine. Scrap locations remove stock from inventory valuation. Quarantined stock is not scrapped — it's under review. It might be reworked, returned to vendor, or eventually scrapped after investigation. Use a regular internal location so the stock remains in your valuation reports and on-hand quantity calculations until a disposition decision is made.

05

Integrating Quality Control with Manufacturing and Inventory in Odoo 19

Quality control becomes powerful when it is woven into your manufacturing and inventory operations — not bolted on as a separate process. Odoo 19's quality_mrp module connects quality checks to work orders, enabling in-process inspections that catch defects mid-production rather than at the end.

Work Order Quality Checks

When you create a QCP with the operation type set to a manufacturing operation, Odoo adds quality checks as steps within the work order. The operator cannot proceed to the next manufacturing step until the quality check is completed. This is how you enforce inspection gates in production.

Python — models/mrp_quality_integration.py
from odoo import models, fields, api


class MrpProductionQuality(models.Model):
    """Add quality summary fields to manufacturing orders."""
    _inherit = 'mrp.production'

    quality_check_count = fields.Integer(
        compute='_compute_quality_counts',
    )
    quality_alert_count = fields.Integer(
        compute='_compute_quality_counts',
    )
    quality_pass_rate = fields.Float(
        string='Pass Rate (%)',
        compute='_compute_quality_counts',
        digits=(5, 1),
    )

    @api.depends('check_ids', 'check_ids.quality_state')
    def _compute_quality_counts(self):
        for production in self:
            checks = production.check_ids
            production.quality_check_count = len(checks)
            production.quality_alert_count = len(
                checks.filtered(
                    lambda c: c.quality_state == 'fail'
                ).mapped('alert_ids')
            )
            passed = len(
                checks.filtered(
                    lambda c: c.quality_state == 'pass'
                )
            )
            total = len(
                checks.filtered(
                    lambda c: c.quality_state in ('pass', 'fail')
                )
            )
            production.quality_pass_rate = (
                (passed / total * 100) if total else 0.0
            )

Inventory Receipt Inspections

For incoming goods, the standard flow is: Receive → Inspect → Put Away. In Odoo 19, you achieve this with a two-step (or three-step) receipt route where the quality check is triggered on the first step. The product lands in an Input location, gets inspected, and only moves to stock after passing QC. Failed items are quarantined automatically using the logic from Section 04.

Python — models/stock_picking_qc.py
from odoo import models, api


class StockPickingQC(models.Model):
    """Block transfer validation if quality checks are pending."""
    _inherit = 'stock.picking'

    def button_validate(self):
        """Prevent validation if any quality checks are unresolved."""
        for picking in self:
            pending_checks = self.env['quality.check'].search([
                ('picking_id', '=', picking.id),
                ('quality_state', '=', 'none'),
            ])
            if pending_checks:
                return {
                    'type': 'ir.actions.client',
                    'tag': 'display_notification',
                    'params': {
                        'title': 'Pending Quality Checks',
                        'message': (
                            f'{len(pending_checks)} quality check(s) '
                            f'must be completed before validation.'
                        ),
                        'type': 'warning',
                        'sticky': True,
                    },
                }
        return super().button_validate()

Reporting: Quality Dashboard

Use Odoo 19's built-in reporting or create a custom dashboard to track these KPIs:

KPIFormulaTargetWhere to Track
First Pass YieldPassed checks / Total checks x 100> 95%Quality → Reports
Defect Rate by VendorFailed receipts / Total receipts per vendor< 2%Custom dashboard
Alert Resolution TimeAvg days from alert creation to Done stage< 5 daysQuality Alerts pipeline
Quarantine AgingDays stock sits in quarantine location< 14 daysInventory → Reporting
Cpk Index (SPC)Process capability from measure inspections> 1.33Custom field on quality check
Sampling vs. 100% Inspection

Not every unit needs inspection. For high-volume, low-risk items, use AQL sampling plans (Acceptable Quality Level). Odoo does not ship with built-in AQL sampling, but you can implement it by adding a sample_percentage field to your QCPs and overriding the check creation logic to only generate checks for a random subset of received quantities. The rule of thumb: 100% inspection for safety-critical items and new vendors, sampling for established supply chains with proven track records.

06

4 Quality Control Mistakes That Let Defective Product Reach Customers

1

Quarantine Location Is Not Excluded from Available Stock

You create a quarantine location, move defective stock there, and feel good. Then a sales order reserves that stock because Odoo counts all internal locations as available by default. The customer receives the exact product that failed inspection. This happens silently — no error, no warning.

Our Fix

Configure push/pull rules so that the quarantine location is never a source for outbound operations. Alternatively, use a dedicated route that excludes the quarantine location from procurement. The simplest approach: make the quarantine location a child of a non-sellable parent location, or set its scrap_location field to True temporarily (though this affects valuation — use with caution).

2

Quality Checks Created But Never Completed

QCPs generate checks automatically, but nothing forces the warehouse team to complete them. The receipt is validated, stock enters the main location, and the quality check sits in "To Do" status forever. Your quality control exists on paper but not in practice.

Our Fix

Use the button_validate override from Section 05 to block transfer validation until all quality checks are completed. This is a hard gate — the operator cannot move stock past the inspection stage without recording a result. For manufacturing, the work order step approach enforces this naturally.

3

No Disposition Workflow for Quarantined Stock

Stock gets quarantined but nobody decides what happens next. After 6 months, your quarantine location has 200 SKUs and $50,000 in frozen inventory. The quality team says "we're investigating." The finance team says "where did our inventory go?" The warehouse team says "we're out of space."

Our Fix

Create a disposition workflow with three outcomes: Rework (move to manufacturing), Return to Vendor (create RMA), or Scrap (move to scrap location). Attach a scheduled action that flags quarantine alerts older than 14 days. No stock should sit in quarantine without a disposition decision for more than 2 weeks.

4

Measurement Tolerances Set Incorrectly in the QCP

A measure-type QCP has norm, tolerance_min, and tolerance_max fields. We have seen setups where the tolerance is entered as the absolute value instead of the boundary value. Example: a shaft should be 25.0mm +/- 0.1mm. The correct config is norm=25.0, tolerance_min=24.9, tolerance_max=25.1. But teams enter tolerance_min=0.1, tolerance_max=0.1 — and every measurement over 0.1mm fails.

Our Fix

Add a validation constraint on the QCP that checks tolerance_min < norm < tolerance_max. Also add an onchange helper that auto-fills boundaries when the user enters the norm and a +/- delta value. This prevents the most common data entry error in measure inspections.

BUSINESS ROI

What Automated Quality Control Saves Your Business

Quality failures are expensive. A defect caught at incoming inspection costs cents. The same defect caught by a customer costs dollars — in returns, replacements, support time, and lost trust. Here is the math:

60%Fewer Customer Returns

Catching defects at receipt instead of after shipping eliminates the most expensive failure mode: returns with shipping, restocking, and customer apology costs.

$0Spreadsheet QC Overhead

Replace manual defect tracking in Google Sheets with automated alerts, quarantine transfers, and disposition workflows — all inside Odoo.

100%Audit Trail Coverage

Every inspection, failure, quarantine move, and corrective action is logged with timestamp, user, and linked documents. ISO 9001 and FDA auditors get what they need instantly.

The hidden ROI: vendor accountability. When every incoming defect is tracked by vendor, lot, and severity, you have data-driven leverage in vendor negotiations. "Your defect rate was 8% last quarter — here are the 47 quality alerts with photos" is a very different conversation than "we think there were some quality issues."

SEO NOTES

Optimization Metadata

Meta Desc

Complete guide to quality control in Odoo 19. Configure quality control points, pass/fail and measure inspections, quality alerts, and automated quarantine workflows.

H2 Keywords

1. "Setting Up Quality Control Points in Odoo 19: Where, When, and What to Inspect"
2. "Odoo 19 Inspection Types: Pass/Fail, Measure, and Picture-Based Quality Checks"
3. "Automated Quarantine Workflows: Moving Failed Stock Without Human Intervention"
4. "4 Quality Control Mistakes That Let Defective Product Reach Customers"

Stop Shipping Defective Product

Quality control is not a checkbox on your ERP feature list — it's the difference between a customer who reorders and a customer who posts a one-star review. Odoo 19 gives you the tools to catch defects at every stage: incoming receipt, manufacturing work order, and outbound picking. But the tools only work if they are configured correctly: the right control points, the right inspection types, enforced completion gates, and automated quarantine that does not rely on someone remembering to create a transfer.

If your quality process lives in spreadsheets or depends on manual stock moves, we can help. We implement end-to-end quality control workflows in Odoo 19 — from incoming inspection through quarantine, disposition, and vendor scorecarding. The setup typically takes 2-3 weeks and starts paying for itself the first time it catches a defective shipment before it reaches a customer.

Book a Free Quality Audit