Skip to content

QC Reason Documentation

Overview

The QC Reason module (qc.reason) provides a simple lookup table for quality control rejection reasons. This allows standardization of QC failure reasons across the organization for reporting and analysis.


Model Information

Model Name: qc.reason
Display Name: QC Reason
Key Fields: None (commented out in code)

Features

  • ❌ Audit logging enabled (_audit_log)
  • ❌ Multi-company support (commented out in code)
  • ❌ Full-text content search (_content_search)
  • ❌ Unique key constraint (commented out in code)

Commented Features

The model has commented code suggesting these features were considered but not implemented:

#_key=["name"]          # Would enforce unique reason names
#_multi_company=True    # Would enable company-specific reasons


Field Reference

Header Fields

Field Type Required Description
name Char Name/description of the QC rejection reason (searchable)

Common QC Reason Examples

Typical QC reasons that might be configured:

Category Example Reasons
Visual Defects Scratches, Dents, Discoloration, Surface Contamination
Dimensional Undersized, Oversized, Out of Tolerance, Warpage
Material Wrong Material, Material Defect, Contamination
Functional Does Not Function, Performance Below Spec, Leaks
Packaging Damaged Packaging, Missing Components, Wrong Labeling
Process Incomplete Processing, Wrong Process Applied, Rework Required

API Methods

1. Create QC Reason

Method: create(vals, context)

Creates a new QC reason record.

Parameters:

vals = {
    "name": "Surface Scratches"    # Reason description
}

Returns: int - New reason ID

Example:

# Create standard QC reasons
reasons = [
    "Surface Scratches",
    "Dimensional Out of Tolerance",
    "Material Contamination",
    "Incomplete Processing",
    "Damaged Packaging"
]

for reason_name in reasons:
    get_model("qc.reason").create({"name": reason_name})


Model Relationship Description
qc.result Potential Reference QC results may reference these reasons for rejections
stock.move Indirect QC results link to stock moves which may use these reasons

Common Use Cases

Use Case 1: Initialize Standard Reasons

# Set up company-wide QC reasons
standard_reasons = [
    # Visual defects
    "Surface Scratches",
    "Dents or Deformation",
    "Discoloration",
    "Paint Defects",

    # Dimensional issues
    "Undersized",
    "Oversized",
    "Out of Tolerance",

    # Material issues
    "Wrong Material Grade",
    "Material Contamination",
    "Porosity",

    # Functional issues
    "Failed Performance Test",
    "Does Not Meet Specification",

    # Process issues
    "Incomplete Heat Treatment",
    "Improper Coating",
    "Rework Required"
]

for reason in standard_reasons:
    # Check if exists first
    existing = get_model("qc.reason").search([["name", "=", reason]])
    if not existing:
        get_model("qc.reason").create({"name": reason})

Use Case 2: QC Reason Selection in UI

# Get all reasons for dropdown/selection
def get_qc_reasons():
    reason_ids = get_model("qc.reason").search([])
    reasons = get_model("qc.reason").browse(reason_ids)

    return [(r.id, r.name) for r in reasons]

# Use in QC inspection
qc_reasons = get_qc_reasons()
# Returns: [(1, "Surface Scratches"), (2, "Undersized"), ...]

Use Case 3: QC Reason Reporting

# Analyze rejection patterns (if QC results reference reasons)
def analyze_rejection_reasons():
    """
    Note: This assumes qc.result has a reason_id field
    (not present in provided code but logical extension)
    """

    # Count rejections by reason
    rejection_counts = {}

    # Get all rejected QC results
    qc_results = get_model("qc.result").search([
        ["result", "=", "reject"]
    ])

    for result in get_model("qc.result").browse(qc_results):
        # If reason_id field exists:
        # reason_name = result.reason_id.name
        # rejection_counts.setdefault(reason_name, 0)
        # rejection_counts[reason_name] += 1
        pass

    # Sort by frequency
    sorted_reasons = sorted(
        rejection_counts.items(),
        key=lambda x: x[1],
        reverse=True
    )

    print("Top Rejection Reasons:")
    for reason, count in sorted_reasons[:10]:
        print(f"  {reason}: {count} rejections")

Use Case 4: Reason Category Management

# Organize reasons by category for better reporting
def categorize_reasons():
    all_reasons = get_model("qc.reason").search([])
    reasons = get_model("qc.reason").browse(all_reasons)

    categories = {
        "Visual": [],
        "Dimensional": [],
        "Material": [],
        "Functional": [],
        "Process": [],
        "Other": []
    }

    # Simple keyword-based categorization
    for reason in reasons:
        name_lower = reason.name.lower()

        if any(kw in name_lower for kw in ["scratch", "dent", "visual", "surface", "paint"]):
            categories["Visual"].append(reason)
        elif any(kw in name_lower for kw in ["size", "dimension", "tolerance", "measurement"]):
            categories["Dimensional"].append(reason)
        elif any(kw in name_lower for kw in ["material", "grade", "contamination"]):
            categories["Material"].append(reason)
        elif any(kw in name_lower for kw in ["function", "performance", "test", "specification"]):
            categories["Functional"].append(reason)
        elif any(kw in name_lower for kw in ["process", "treatment", "rework", "incomplete"]):
            categories["Process"].append(reason)
        else:
            categories["Other"].append(reason)

    # Print categorized reasons
    for category, reason_list in categories.items():
        if reason_list:
            print(f"\n{category}:")
            for r in reason_list:
                print(f"  - {r.name}")

Best Practices

1. Use Consistent Naming

# Good: Clear, standardized names
good_reasons = [
    "Surface Scratches - Minor",
    "Surface Scratches - Major",
    "Dimensional Tolerance Exceeded - Length",
    "Dimensional Tolerance Exceeded - Width"
]

# Bad: Inconsistent, vague names
bad_reasons = [
    "scratches",
    "Scratch",
    "bad surface",
    "too big"
]

2. Create Hierarchical Reasons

# Organize reasons in parent-child hierarchy
# (Would require additional parent_id field)
hierarchical_reasons = [
    "Visual Defects",
    "  ├─ Surface Scratches",
    "  ├─ Dents",
    "  └─ Discoloration",
    "Dimensional Issues",
    "  ├─ Undersized",
    "  └─ Oversized"
]

3. Periodic Reason Review

# Identify unused reasons for cleanup
def find_unused_reasons():
    """
    Find reasons that haven't been used in QC results
    (Assumes qc.result has reason_id field)
    """
    all_reason_ids = get_model("qc.reason").search([])

    # Get reasons used in QC results
    # used_reason_ids = set()
    # for result in get_model("qc.result").search([]):
    #     if result.reason_id:
    #         used_reason_ids.add(result.reason_id.id)

    # unused = set(all_reason_ids) - used_reason_ids
    # return unused
    pass

Potential Enhancements

The model could be enhanced with these features:

1. Enable Multi-Company Support

_multi_company = True
_fields = {
    "name": fields.Char("Name", search=True),
    "company_id": fields.Many2One("company", "Company")
}

2. Add Unique Constraint

_key = ["name"]
# or
_key = ["name", "company_id"]  # if multi-company

3. Add Categorization

_fields = {
    "name": fields.Char("Name", search=True, required=True),
    "category": fields.Selection([
        ["visual", "Visual Defects"],
        ["dimensional", "Dimensional Issues"],
        ["material", "Material Issues"],
        ["functional", "Functional Issues"],
        ["process", "Process Issues"]
    ], "Category"),
    "description": fields.Text("Detailed Description"),
    "severity": fields.Selection([
        ["minor", "Minor"],
        ["major", "Major"],
        ["critical", "Critical"]
    ], "Severity Level")
}

4. Add Activity Tracking

_fields = {
    "name": fields.Char("Name", search=True, required=True),
    "active": fields.Boolean("Active", default=True),
    "usage_count": fields.Integer("Usage Count", readonly=True)
}

Search Examples

Find Reasons by Keyword

# Search for reasons containing "scratch"
reason_ids = get_model("qc.reason").search([
    ["name", "ilike", "%scratch%"]
])

Get All Active Reasons

# If active field is added
reason_ids = get_model("qc.reason").search([
    ["active", "=", True]
])

Performance Tips

1. Cache Reason List

# Cache reasons for dropdown lists
_reason_cache = None
_reason_cache_time = None

def get_cached_reasons():
    global _reason_cache, _reason_cache_time

    # Refresh cache every hour
    now = time.time()
    if not _reason_cache or (now - _reason_cache_time) > 3600:
        reason_ids = get_model("qc.reason").search([])
        _reason_cache = get_model("qc.reason").browse(reason_ids)
        _reason_cache_time = now

    return _reason_cache

Troubleshooting

"Duplicate reason names"

Cause: No unique constraint on name field
Solution: - Manually check for duplicates before creating - Consider uncommenting _key = ["name"] to enforce uniqueness

"Too many similar reasons"

Cause: Lack of standardization, multiple people creating reasons
Solution: - Implement approval process for new reasons - Periodically consolidate similar reasons - Create category structure


Version History

Last Updated: October 2025
Model Version: qc_reason.py
Framework: Netforce


Additional Resources

  • QC Result Documentation: qc.result
  • Quality Control Process Documentation
  • Stock Movement Documentation: stock.move

This documentation is generated for developer onboarding and reference purposes.