QC Result Documentation¶
Overview¶
The QC Result module (qc.result) records quality control inspection results for products during stock movements or production. It captures test results, inspector information, and accept/reject decisions with supporting documentation.
Model Information¶
Model Name: qc.result
Display Name: QC Result
Key Fields: None (no unique constraint defined)
Features¶
- ❌ Audit logging enabled (
_audit_log) - ❌ Multi-company support (
company_id) - ❌ Full-text content search (
_content_search) - ❌ Unique key constraint
- ✅ Custom default ordering by date and ID
Sort Order: date,id (most recent first)
Field Reference¶
Header Fields¶
| Field | Type | Required | Description |
|---|---|---|---|
date |
Date | ❌ | Date of quality control inspection (searchable) |
result |
Selection | ✅ | Final QC decision: "accept" or "reject" (searchable) |
total_qty |
Decimal | ❌ | Total quantity being inspected |
sample_qty |
Decimal | ❌ | Sample quantity tested from total |
image |
File | ❌ | Photo documentation of inspection or defects |
Product and Lot Tracking¶
| Field | Type | Description |
|---|---|---|
product_id |
Many2One | Product being inspected (searchable) |
lot_id |
Many2One | Lot or serial number of the product (searchable) |
uom_id |
Many2One | Unit of measure for quantities |
Test Results¶
| Field | Type | Description |
|---|---|---|
test1 |
Selection | Test 1 result: "1" (pass) or "0" (fail) |
test2 |
Selection | Test 2 result: "1" (pass) or "0" (fail) |
test3 |
Selection | Test 3 result: "1" (pass) or "0" (fail) |
test4 |
Selection | Test 4 result: "1" (pass) or "0" (fail) |
test5 |
Selection | Test 5 result: "1" (pass) or "0" (fail) |
test6 |
Selection | Test 6 result: "1" (pass) or "0" (fail) |
test7 |
Selection | Test 7 result: "1" (pass) or "0" (fail) |
test8 |
Selection | Test 8 result: "1" (pass) or "0" (fail) |
Personnel¶
| Field | Type | Description |
|---|---|---|
inspector_id |
Many2One | User who performed the inspection (searchable) |
reviewer_id |
Many2One | User who reviewed the inspection results (searchable) |
Related Records¶
| Field | Type | Description |
|---|---|---|
pick_id |
Many2One | Related stock picking (on_delete="cascade", searchable) |
production_id |
Many2One | Related production order (on_delete="cascade", searchable) |
move_id |
Many2One | Specific stock move being inspected |
QC Result Types¶
| Type | Code | Description |
|---|---|---|
| Accept | accept |
Product passed quality inspection and is approved |
| Reject | reject |
Product failed quality inspection and is rejected |
Test Result Values¶
Each test (test1-test8) can have two values:
| Value | Code | Meaning |
|---|---|---|
| Pass | 1 |
Test passed |
| Fail | 0 |
Test failed |
The tests are generic and can be configured to represent specific quality checks relevant to the business (e.g., dimensional accuracy, visual inspection, functional test, etc.).
API Methods¶
1. Create QC Result¶
Method: create(vals, context)
Creates a new quality control result record.
Parameters:
vals = {
"date": "2025-10-27", # Inspection date
"product_id": 123, # Product being inspected
"lot_id": 456, # Lot/serial number
"total_qty": 100.0, # Total quantity
"sample_qty": 10.0, # Sample size
"uom_id": 1, # Unit of measure
"test1": "1", # Test results
"test2": "1",
"test3": "0", # Failed test
"result": "reject", # Overall result
"inspector_id": 789, # Inspector
"pick_id": 101, # Related picking (optional)
"move_id": 202, # Related stock move (optional)
}
Returns: int - New QC result ID
Example:
# Record a QC inspection result
qc_result_id = get_model("qc.result").create({
"date": "2025-10-27",
"product_id": product_id,
"lot_id": lot_id,
"total_qty": 100.0,
"sample_qty": 10.0,
"uom_id": uom_id,
"test1": "1", # Pass
"test2": "1", # Pass
"test3": "1", # Pass
"test4": "1", # Pass
"result": "accept",
"inspector_id": user_id,
"pick_id": picking_id
})
2. Default Get (Pre-fill from Stock Move)¶
Method: default_get(field_names, context, **kw)
Automatically populates QC result fields from a stock move when creating from a move context.
Context:
Behavior: - Retrieves stock move details - Pre-fills product, lot, quantity, UoM, and picking information - Simplifies QC result creation from stock movement screens
Example:
# Create QC result from stock move context
qc_result_id = get_model("qc.result").create(
{},
context={"move_id": move_id}
)
# Product, lot, qty, etc. are automatically filled from the move
Pre-filled Fields:
- pick_id - From move.picking_id
- move_id - The stock move itself
- product_id - From move.product_id
- lot_id - From move.lot_id (if exists)
- total_qty - From move.qty
- uom_id - From move.uom_id
3. Dummy Method¶
Method: dummy(ids, context)
Placeholder method with no functionality. May be used for UI interactions or future implementation.
Related Models¶
| Model | Relationship | Description |
|---|---|---|
stock.picking |
Many2One | Stock picking operation being inspected |
production.order |
Many2One | Production order being inspected |
stock.move |
Many2One | Specific stock movement being inspected |
product |
Many2One | Product being inspected |
stock.lot |
Many2One | Lot/serial number of product |
uom |
Many2One | Unit of measure for quantities |
base.user |
Many2One | Inspector and reviewer users |
Common Use Cases¶
Use Case 1: Record Incoming Material Inspection¶
# QC inspection on received materials
def record_receiving_inspection(picking_id, move_id):
move = get_model("stock.move").browse(move_id)
qc_result_id = get_model("qc.result").create({
"date": time.strftime("%Y-%m-%d"),
"pick_id": picking_id,
"move_id": move_id,
"product_id": move.product_id.id,
"lot_id": move.lot_id.id if move.lot_id else None,
"total_qty": move.qty,
"sample_qty": min(10, move.qty), # Sample size
"uom_id": move.uom_id.id,
"test1": "1", # Packaging condition
"test2": "1", # Visual inspection
"test3": "1", # Documentation check
"result": "accept",
"inspector_id": get_active_user(),
})
return qc_result_id
Use Case 2: Production Output Inspection¶
# QC inspection on finished goods
def record_production_inspection(production_id, product_id, qty):
qc_result_id = get_model("qc.result").create({
"date": time.strftime("%Y-%m-%d"),
"production_id": production_id,
"product_id": product_id,
"total_qty": qty,
"sample_qty": 5,
"test1": "1", # Dimensional check
"test2": "1", # Functional test
"test3": "0", # Visual defects detected
"test4": "1", # Performance test
"result": "reject", # Failed due to test3
"inspector_id": get_active_user(),
}, context={"production_id": production_id})
return qc_result_id
Use Case 3: Create QC Result from Stock Move UI¶
# When user clicks "Create QC Result" on a stock move
# The system automatically pre-fills fields
# In the UI action:
action = {
"name": "QC Result",
"view": "qc_result_form",
"model": "qc.result",
"context": {
"move_id": current_move_id # Triggers default_get
}
}
# default_get automatically populates:
# - Product
# - Lot
# - Quantity
# - UoM
# - Picking reference
Use Case 4: Batch Inspection with Photo Documentation¶
# Record inspection with photo evidence
def record_inspection_with_photo(move_id, test_results, photo_path):
move = get_model("stock.move").browse(move_id)
# Determine overall result based on tests
all_passed = all(result == "1" for result in test_results.values())
qc_result_id = get_model("qc.result").create({
"date": time.strftime("%Y-%m-%d"),
"move_id": move_id,
"pick_id": move.picking_id.id,
"product_id": move.product_id.id,
"lot_id": move.lot_id.id if move.lot_id else None,
"total_qty": move.qty,
"sample_qty": test_results.get("sample_qty", 1),
"test1": test_results.get("test1"),
"test2": test_results.get("test2"),
"test3": test_results.get("test3"),
"test4": test_results.get("test4"),
"result": "accept" if all_passed else "reject",
"inspector_id": get_active_user(),
"image": photo_path, # Photo evidence
})
return qc_result_id
Use Case 5: QC Result Analysis¶
# Analyze QC performance metrics
def analyze_qc_results(date_from, date_to):
qc_results = get_model("qc.result").search([
["date", ">=", date_from],
["date", "<=", date_to]
])
total = len(qc_results)
accepted = 0
rejected = 0
test_failures = {f"test{i}": 0 for i in range(1, 9)}
for result in get_model("qc.result").browse(qc_results):
if result.result == "accept":
accepted += 1
else:
rejected += 1
# Count test failures
for i in range(1, 9):
test_field = f"test{i}"
test_value = getattr(result, test_field, None)
if test_value == "0":
test_failures[test_field] += 1
acceptance_rate = (accepted / total * 100) if total > 0 else 0
print(f"QC Results from {date_from} to {date_to}:")
print(f" Total inspections: {total}")
print(f" Accepted: {accepted} ({acceptance_rate:.1f}%)")
print(f" Rejected: {rejected} ({100-acceptance_rate:.1f}%)")
print(f"\nTest Failure Breakdown:")
for test, count in test_failures.items():
if count > 0:
print(f" {test}: {count} failures")
Use Case 6: Inspector Performance Report¶
# Track inspector activity and accuracy
def inspector_performance_report(inspector_id, date_from, date_to):
results = get_model("qc.result").search([
["inspector_id", "=", inspector_id],
["date", ">=", date_from],
["date", "<=", date_to]
])
inspector = get_model("base.user").browse(inspector_id)
qc_records = get_model("qc.result").browse(results)
total_inspections = len(qc_records)
accepted = sum(1 for r in qc_records if r.result == "accept")
rejected = sum(1 for r in qc_records if r.result == "reject")
print(f"Inspector: {inspector.name}")
print(f"Period: {date_from} to {date_to}")
print(f"Total Inspections: {total_inspections}")
print(f"Accepted: {accepted}")
print(f"Rejected: {rejected}")
print(f"Rejection Rate: {(rejected/total_inspections*100):.1f}%")
Understanding Test Fields¶
The model provides 8 generic test fields (test1-test8) that can be configured to represent specific quality checks:
Example Test Configurations¶
Manufacturing Company: - test1: Dimensional Accuracy - test2: Visual Inspection - test3: Functional Test - test4: Performance Verification - test5: Safety Check - test6: Packaging Quality - test7: Documentation Complete - test8: Labeling Correct
Food Processing: - test1: Temperature Check - test2: Visual Appearance - test3: Taste Test - test4: Texture Evaluation - test5: Color Match - test6: Packaging Seal - test7: Expiry Date Check - test8: Barcode Verification
Electronics: - test1: Power-On Test - test2: Functional Test - test3: Calibration Check - test4: Signal Quality - test5: Physical Inspection - test6: Firmware Version - test7: Packaging Condition - test8: Documentation
Best Practices¶
1. Consistent Test Definitions¶
# Define test meanings in documentation or settings
QC_TEST_DEFINITIONS = {
"test1": "Dimensional Accuracy",
"test2": "Visual Inspection",
"test3": "Functional Test",
"test4": "Performance Test",
"test5": "Safety Check",
"test6": "Packaging Quality",
"test7": "Documentation",
"test8": "Labeling"
}
# Use consistently across all inspections
2. Determine Result from Tests¶
# Automatically set result based on test outcomes
def create_qc_with_auto_result(vals):
test_results = [
vals.get(f"test{i}")
for i in range(1, 9)
if vals.get(f"test{i}") is not None
]
# If any test failed, mark as reject
if "0" in test_results:
vals["result"] = "reject"
else:
vals["result"] = "accept"
return get_model("qc.result").create(vals)
3. Require Inspector for All Results¶
# Validate inspector is set
def create_qc_validated(vals):
if not vals.get("inspector_id"):
raise Exception("Inspector must be specified for QC result")
return get_model("qc.result").create(vals)
4. Link Photos for Rejections¶
# Require photo for rejected items
def create_qc_with_photo_check(vals):
if vals.get("result") == "reject" and not vals.get("image"):
# Warning or requirement
print("Warning: Photo recommended for rejected items")
return get_model("qc.result").create(vals)
Search Functions¶
Search by Result Type¶
# Find all rejected items
rejected_ids = get_model("qc.result").search([
["result", "=", "reject"]
])
Search by Product¶
# Find all QC results for a specific product
product_qc_ids = get_model("qc.result").search([
["product_id", "=", product_id]
])
Search by Date Range¶
# Find QC results in date range
date_range_ids = get_model("qc.result").search([
["date", ">=", "2025-01-01"],
["date", "<=", "2025-12-31"]
])
Search by Inspector¶
# Find all inspections by specific inspector
inspector_qc_ids = get_model("qc.result").search([
["inspector_id", "=", inspector_id]
])
Search by Lot¶
# Find QC results for specific lot
lot_qc_ids = get_model("qc.result").search([
["lot_id", "=", lot_id]
])
Integration Points¶
Stock Picking Integration¶
QC results can be linked to stock pickings (receiving, delivery, internal transfers) to ensure quality checks are performed and recorded.
Production Order Integration¶
QC results can be linked to production orders to verify output quality before releasing finished goods.
Stock Move Integration¶
The default_get method provides seamless integration with stock moves, allowing QC results to be created directly from movement screens.
Workflow Integration¶
QC results can trigger workflow actions:
# Example workflow triggers (if implemented)
def on_qc_result_create(qc_result):
if qc_result.result == "reject":
# Trigger rejection workflow
# - Hold stock movement
# - Notify quality manager
# - Create corrective action
pass
elif qc_result.result == "accept":
# Trigger acceptance workflow
# - Release stock movement
# - Update inventory
pass
Performance Tips¶
1. Use Batch Processing for Multiple Inspections¶
# Process multiple QC results efficiently
def batch_create_qc_results(qc_data_list):
qc_ids = []
for qc_data in qc_data_list:
qc_id = get_model("qc.result").create(qc_data)
qc_ids.append(qc_id)
return qc_ids
2. Index Frequently Searched Fields¶
The model already marks key fields as searchable (date, product_id, lot_id, result, inspector_id). Ensure database indexes exist for optimal performance.
Troubleshooting¶
"QC result not auto-populating from move"¶
Cause: Missing or incorrect move_id in context
Solution: Ensure context contains {"move_id": <valid_move_id>} when creating
"Cannot link to deleted picking/production"¶
Cause: on_delete="cascade" means QC results are deleted when parent is deleted
Solution:
- This is intended behavior for data integrity
- Export QC results before deleting parent records if history is needed
"Test results not affecting final result"¶
Cause: No automatic logic linking test outcomes to final result
Solution: Implement custom logic to auto-set result based on test values
Security Considerations¶
Permission Model¶
Consider implementing permissions for: - Creating QC results (QC inspectors) - Reviewing QC results (QC managers) - Modifying QC results (limited or audit-only) - Deleting QC results (restricted)
Data Integrity¶
- QC results should generally be immutable after creation
- Changes should be audited
- Consider adding state workflow (draft → confirmed → reviewed)
Potential Enhancements¶
1. Add QC Reason Field¶
"reason_id": fields.Many2One("qc.reason", "Rejection Reason"),
"notes": fields.Text("Additional Notes")
2. Add State Workflow¶
"state": fields.Selection([
["draft", "Draft"],
["confirmed", "Confirmed"],
["reviewed", "Reviewed"]
], "Status")
3. Add Corrective Actions¶
"corrective_action": fields.Text("Corrective Action Required"),
"action_completed": fields.Boolean("Action Completed")
4. Add Statistical Fields¶
"defect_rate": fields.Decimal("Defect Rate %", function="calc_defect_rate"),
"pass_rate": fields.Decimal("Pass Rate %", function="calc_pass_rate")
Version History¶
Last Updated: October 2025
Model Version: qc_result.py
Framework: Netforce
Copyright: 2012-2015 Netforce Co. Ltd. (MIT License)
Additional Resources¶
- QC Reason Documentation:
qc.reason - Stock Picking Documentation:
stock.picking - Production Order Documentation:
production.order - Stock Move Documentation:
stock.move
This documentation is generated for developer onboarding and reference purposes.