Stock Grade Line Documentation¶
Overview¶
The Stock Grade Line module (stock.grade.line) represents individual graded product lines within a grading operation. Each line specifies a graded product, its quantity, grade quality breakdown (Grade-A, Grade-B, waste), and cost allocation.
Model Information¶
Model Name: stock.grade.line
Display Name: (Not explicitly defined)
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
Field Reference¶
Header Fields¶
| Field | Type | Required | Description |
|---|---|---|---|
grade_id |
Many2One | ✅ | Parent grading operation (on_delete="cascade") |
product_id |
Many2One | ✅ | Graded product SKU (stock type only, searchable) |
qty |
Decimal | ✅ | Total quantity of this graded product |
uom_id |
Many2One | ❌ | Unit of measure |
location_id |
Many2One | ✅ | Destination location for this graded product (searchable) |
Grade Breakdown Fields¶
| Field | Type | Description |
|---|---|---|
qty_ga |
Decimal | Quantity classified as Grade-A quality |
qty_gb |
Decimal | Quantity classified as Grade-B quality |
product_gb_id |
Many2One | Separate product SKU for Grade-B (if different from main product) |
qty_waste |
Decimal | Quantity classified as waste/scrap |
qty_loss |
Decimal | Computed: Quantity lost or unaccounted for |
Lot Tracking¶
| Field | Type | Description |
|---|---|---|
lot_id |
Many2One | Lot or serial number for traceability (searchable) |
Cost Tracking¶
| Field | Type | Description |
|---|---|---|
unit_price |
Decimal | Cost per unit for this graded product |
amount |
Decimal | Total cost for this line (qty × unit_price) |
Related Documents¶
| Field | Type | Description |
|---|---|---|
related_id |
Reference | Generic reference to source: picking or production order |
picking_id |
Many2One | Source stock picking (if grading from receiving) |
purchase_id |
Many2One | Source purchase order |
production_id |
Many2One | Source production order |
qty_remain |
Decimal | Computed: Remaining quantity to grade from source |
Computed Fields Functions¶
get_qty_loss(ids, context)¶
Calculates quantity lost or unaccounted for in this grading line.
Formula:
Example:
# Line: qty=100, qty_ga=80, qty_gb=15, qty_waste=3
# qty_loss = 100 - 80 - 15 - 3 = 2
line = get_model("stock.grade.line").browse(line_id)
print(f"Loss: {line.qty_loss}") # 2
Returns: Dictionary mapping line ID to loss quantity
get_qty_remain(ids, context)¶
Calculates remaining quantity that still needs to be graded from the source purchase order.
Logic: 1. Identifies all purchase orders linked to grading lines 2. For each purchase order: - Sums total quantities received across all PO lines - Sums total quantities already graded across all grading records 3. Calculates remaining = received - graded
Complex Multi-Step Process: 1. Collects purchase IDs from all lines being queried 2. Retrieves quantity received per (purchase_id, product_id) combination 3. Finds all grading records linked to those purchases 4. Sums graded quantities per (purchase_id, product_id) 5. Computes remaining for each line
Returns: Dictionary mapping line ID to remaining quantity
Example:
# Purchase Order received: 1000kg of Product A
# Grading 1: 400kg graded
# Grading 2 (current): 300kg graded
# qty_remain = 1000 - 400 - 300 = 300kg
line = get_model("stock.grade.line").browse(line_id)
print(f"Remaining to grade: {line.qty_remain}kg")
get_amount(ids, context)¶
Calculates line amount based on quantity and unit price.
Formula:
Note: This function is defined but not actively used as a computed field in the model definition. Amount is typically calculated via onchange events instead.
API Methods¶
1. Create Grade Line¶
Method: create(vals, context)
Creates a new grading line record.
Parameters:
vals = {
"grade_id": 1, # Required: parent grading
"product_id": 123, # Required: graded product
"qty": 800.0, # Required: quantity
"location_id": 10, # Required: destination
"lot_id": 456, # Optional: lot tracking
"qty_ga": 750.0, # Optional: Grade-A breakdown
"qty_gb": 40.0, # Optional: Grade-B breakdown
"qty_waste": 10.0, # Optional: waste
"unit_price": 12.50, # Optional: cost
"amount": 10000.00, # Optional: total cost
"purchase_id": 789, # Optional: source PO
}
Returns: int - New line ID
Example:
# Create grading line for Grade-A product
line_id = get_model("stock.grade.line").create({
"grade_id": grade_id,
"product_id": grade_a_product_id,
"qty": 800.0,
"location_id": grade_a_location_id,
"qty_ga": 800.0, # All is Grade-A
"qty_gb": 0.0,
"qty_waste": 0.0,
"unit_price": 12.50,
"amount": 10000.00,
"purchase_id": po_id,
"lot_id": lot_id
})
Related Models¶
| Model | Relationship | Description |
|---|---|---|
stock.grade |
Many2One | Parent grading operation |
product |
Many2One | Graded product and optional Grade-B product |
stock.location |
Many2One | Destination location for graded product |
stock.lot |
Many2One | Lot/serial number for traceability |
uom |
Many2One | Unit of measure |
stock.picking |
Many2One/Reference | Source picking |
purchase.order |
Many2One | Source purchase order |
production.order |
Many2One | Source production order |
Common Use Cases¶
Use Case 1: Create Multi-Grade Line¶
# When a graded product has mixed quality
line_id = get_model("stock.grade.line").create({
"grade_id": grade_id,
"product_id": graded_product_id,
"qty": 1000.0,
"location_id": main_storage_id,
"qty_ga": 850.0, # 85% Grade-A
"qty_gb": 130.0, # 13% Grade-B
"qty_waste": 15.0, # 1.5% waste
# qty_loss = 5.0 (computed: 0.5% loss)
"unit_price": 10.00,
"amount": 10000.00,
"lot_id": lot_id
})
Use Case 2: Separate Grade-B Product¶
# When Grade-B uses different product SKU
# Line 1: Grade-A
line_a_id = get_model("stock.grade.line").create({
"grade_id": grade_id,
"product_id": grade_a_sku_id,
"qty": 850.0,
"location_id": grade_a_location_id,
"qty_ga": 850.0,
"unit_price": 12.00,
"amount": 10200.00
})
# Line 2: Grade-B with separate SKU
line_b_id = get_model("stock.grade.line").create({
"grade_id": grade_id,
"product_id": grade_b_sku_id, # Different product
"qty": 130.0,
"location_id": grade_b_location_id,
"qty_gb": 130.0,
"unit_price": 7.00,
"amount": 910.00,
"product_gb_id": grade_b_sku_id # Reference to Grade-B product
})
# Line 3: Waste
line_w_id = get_model("stock.grade.line").create({
"grade_id": grade_id,
"product_id": waste_sku_id,
"qty": 15.0,
"location_id": waste_location_id,
"qty_waste": 15.0,
"unit_price": 0.00,
"amount": 0.00
})
Use Case 3: Track Grading from Purchase Order¶
# Create grading lines linked to purchase order
def create_grading_from_po(po_id, grade_id):
po = get_model("purchase.order").browse(po_id)
for po_line in po.lines:
if po_line.qty_received > 0:
# Create grading line
get_model("stock.grade.line").create({
"grade_id": grade_id,
"product_id": po_line.product_id.id,
"qty": po_line.qty_received,
"location_id": grade_a_location_id,
"purchase_id": po_id,
"unit_price": po_line.unit_price,
"amount": po_line.qty_received * po_line.unit_price
})
# Check remaining quantity to grade
grade = get_model("stock.grade").browse(grade_id)
for line in grade.lines:
if line.qty_remain > 0:
print(f"Product {line.product_id.name}: {line.qty_remain} remaining to grade")
Use Case 4: Cost Allocation Based on Grade¶
# Allocate costs differently based on grade quality
def allocate_grading_costs(total_cost, qty_ga, qty_gb, qty_waste):
"""
Allocate total cost across grades
Grade-A: Full cost
Grade-B: 60% of full cost
Waste: Zero cost
"""
total_weighted = qty_ga + (qty_gb * 0.6)
if total_weighted == 0:
return 0, 0, 0
unit_cost = total_cost / total_weighted
cost_ga = qty_ga * unit_cost
cost_gb = qty_gb * unit_cost * 0.6
cost_waste = 0
return cost_ga / qty_ga if qty_ga else 0, \
cost_gb / qty_gb if qty_gb else 0, \
0
# Apply to grading lines
grade_a_unit_price, grade_b_unit_price, waste_unit_price = \
allocate_grading_costs(10000.00, 850.0, 130.0, 15.0)
# Create lines with calculated prices
lines = [
{
"product_id": grade_a_id,
"qty": 850.0,
"unit_price": grade_a_unit_price,
"amount": 850.0 * grade_a_unit_price
},
{
"product_id": grade_b_id,
"qty": 130.0,
"unit_price": grade_b_unit_price,
"amount": 130.0 * grade_b_unit_price
},
{
"product_id": waste_id,
"qty": 15.0,
"unit_price": 0.00,
"amount": 0.00
}
]
Use Case 5: Analyze Grade Distribution¶
# Analyze grading patterns
def analyze_grade_distribution(product_id, date_from, date_to):
# Find all grading lines for product in date range
line_ids = get_model("stock.grade.line").search([
["product_id", "=", product_id],
["grade_id.date", ">=", date_from],
["grade_id.date", "<=", date_to],
["grade_id.state", "=", "done"]
])
total_qty = 0
total_ga = 0
total_gb = 0
total_waste = 0
total_loss = 0
for line in get_model("stock.grade.line").browse(line_ids):
total_qty += line.qty or 0
total_ga += line.qty_ga or 0
total_gb += line.qty_gb or 0
total_waste += line.qty_waste or 0
total_loss += line.qty_loss or 0
print(f"Grade Distribution Analysis:")
print(f" Total Graded: {total_qty}")
print(f" Grade-A: {total_ga} ({total_ga/total_qty*100:.1f}%)")
print(f" Grade-B: {total_gb} ({total_gb/total_qty*100:.1f}%)")
print(f" Waste: {total_waste} ({total_waste/total_qty*100:.1f}%)")
print(f" Loss: {total_loss} ({total_loss/total_qty*100:.1f}%)")
Understanding Grade Breakdown¶
Each grading line can track quality distribution:
Total Quantity: 1000 units
├─ Grade-A (qty_ga): 850 units (85%)
├─ Grade-B (qty_gb): 130 units (13%)
├─ Waste (qty_waste): 15 units (1.5%)
└─ Loss (qty_loss): 5 units (0.5%) [computed]
This allows: - Cost allocation based on quality - Inventory tracking by grade - Quality analysis over time - Loss identification and monitoring
Best Practices¶
1. Account for All Quantities¶
# Good: All quantity accounted for
line = {
"qty": 1000.0,
"qty_ga": 850.0,
"qty_gb": 130.0,
"qty_waste": 20.0
# qty_loss will be 0
}
# Bad: Large unaccounted loss
line = {
"qty": 1000.0,
"qty_ga": 850.0,
"qty_gb": 130.0,
"qty_waste": 0.0
# qty_loss will be 20 - investigate!
}
2. Use Lot Tracking¶
# Enable traceability through lot tracking
line = {
"grade_id": grade_id,
"product_id": product_id,
"qty": 500.0,
"lot_id": source_lot_id, # Track back to source
"location_id": dest_location_id
}
# Later: trace graded products back to source
def trace_grade_source(lot_id):
lines = get_model("stock.grade.line").search([
["lot_id", "=", lot_id]
])
for line in get_model("stock.grade.line").browse(lines):
print(f"Lot {lot_id} graded in {line.grade_id.number}")
print(f" Qty: {line.qty}")
print(f" Grade-A: {line.qty_ga}")
print(f" Source PO: {line.purchase_id.number if line.purchase_id else 'N/A'}")
3. Consistent Cost Allocation¶
# Establish standard cost allocation rules
GRADE_COST_FACTORS = {
"grade_a": 1.0, # 100% of base cost
"grade_b": 0.6, # 60% of base cost
"waste": 0.0 # No cost
}
def calculate_line_costs(base_unit_cost, qty_ga, qty_gb, qty_waste):
return {
"grade_a_unit_price": base_unit_cost * GRADE_COST_FACTORS["grade_a"],
"grade_b_unit_price": base_unit_cost * GRADE_COST_FACTORS["grade_b"],
"waste_unit_price": base_unit_cost * GRADE_COST_FACTORS["waste"]
}
4. Monitor Loss Rates¶
# Set acceptable loss thresholds
MAX_ACCEPTABLE_LOSS_PCT = 2.0 # 2%
def validate_loss_rate(line):
if line.qty == 0:
return True
loss_pct = (line.qty_loss / line.qty) * 100
if loss_pct > MAX_ACCEPTABLE_LOSS_PCT:
print(f"⚠ Warning: Line {line.id} has {loss_pct:.1f}% loss (threshold: {MAX_ACCEPTABLE_LOSS_PCT}%)")
return False
return True
Search Functions¶
Search by Product¶
# Find all grading lines for a product
line_ids = get_model("stock.grade.line").search([
["product_id", "=", product_id]
])
Search by Location¶
# Find all grading lines going to a location
line_ids = get_model("stock.grade.line").search([
["location_id", "=", location_id]
])
Search by Purchase Order¶
# Find all grading lines from a purchase order
line_ids = get_model("stock.grade.line").search([
["purchase_id", "=", po_id]
])
Search by Lot¶
# Find all grading lines for a lot
line_ids = get_model("stock.grade.line").search([
["lot_id", "=", lot_id]
])
Search by Date Range (via parent)¶
# Find grading lines in date range
line_ids = get_model("stock.grade.line").search([
["grade_id.date", ">=", "2025-01-01"],
["grade_id.date", "<=", "2025-12-31"],
["grade_id.state", "=", "done"]
])
Performance Tips¶
1. Batch Line Processing¶
# Good: Create multiple lines in one transaction
lines_data = [
{"product_id": prod_a_id, "qty": 800.0, ...},
{"product_id": prod_b_id, "qty": 150.0, ...},
{"product_id": waste_id, "qty": 20.0, ...}
]
for line_data in lines_data:
get_model("stock.grade.line").create(line_data)
2. Optimize qty_remain Calculation¶
The get_qty_remain function performs complex multi-model queries. Cache results when possible:
# Cache remaining quantities for a grading session
def get_remaining_cache(purchase_ids):
cache = {}
for po_id in purchase_ids:
po = get_model("purchase.order").browse(po_id)
for line in po.lines:
key = (po_id, line.product_id.id)
cache[key] = line.qty_received
# Subtract already graded
grade_lines = get_model("stock.grade.line").search([
["purchase_id", "in", purchase_ids]
])
for line in get_model("stock.grade.line").browse(grade_lines):
key = (line.purchase_id.id, line.product_id.id)
if key in cache:
cache[key] -= line.qty
return cache
Troubleshooting¶
"qty_loss is unexpectedly high"¶
Cause: Sum of qty_ga, qty_gb, and qty_waste doesn't match total qty
Solution:
- Review grading data for accuracy
- Check if waste quantity was properly recorded
- Investigate potential measurement errors
"qty_remain shows negative value"¶
Cause: More quantity graded than received in purchase order
Solution:
- Verify purchase order received quantities
- Check for duplicate grading entries
- Review grading quantities for errors
"Cost allocation doesn't sum to total"¶
Cause: Inconsistent unit_price calculations across lines
Solution:
- Use standard cost allocation formula
- Ensure all lines account for total cost
- Consider rounding errors in calculations
"Cannot find grading lines by date"¶
Cause: Searching on line directly instead of through parent
Solution:
# Correct: Search through parent relationship
line_ids = get_model("stock.grade.line").search([
["grade_id.date", ">=", date_from],
["grade_id.date", "<=", date_to]
])
Integration Points¶
Purchase Order Integration¶
- Links grading lines to source purchase orders
- Tracks remaining quantities to grade
- Enables cost traceability from procurement
Production Order Integration¶
- Links grading lines to production outputs
- Tracks quality of manufactured goods
- Supports yield analysis
Lot/Serial Tracking¶
- Maintains traceability through lot numbers
- Enables recall management
- Supports quality tracking by batch
Reporting Examples¶
Grade Quality Report¶
def generate_quality_report(date_from, date_to):
line_ids = get_model("stock.grade.line").search([
["grade_id.date", ">=", date_from],
["grade_id.date", "<=", date_to],
["grade_id.state", "=", "done"]
])
by_product = {}
for line in get_model("stock.grade.line").browse(line_ids):
prod_name = line.product_id.name
if prod_name not in by_product:
by_product[prod_name] = {
"total": 0, "ga": 0, "gb": 0,
"waste": 0, "loss": 0
}
by_product[prod_name]["total"] += line.qty or 0
by_product[prod_name]["ga"] += line.qty_ga or 0
by_product[prod_name]["gb"] += line.qty_gb or 0
by_product[prod_name]["waste"] += line.qty_waste or 0
by_product[prod_name]["loss"] += line.qty_loss or 0
# Print report
print("Quality Report")
print("=" * 80)
for product, data in by_product.items():
print(f"\n{product}:")
print(f" Total: {data['total']}")
print(f" Grade-A: {data['ga']} ({data['ga']/data['total']*100:.1f}%)")
print(f" Grade-B: {data['gb']} ({data['gb']/data['total']*100:.1f}%)")
print(f" Waste: {data['waste']} ({data['waste']/data['total']*100:.1f}%)")
print(f" Loss: {data['loss']} ({data['loss']/data['total']*100:.1f}%)")
Cost Analysis Report¶
def generate_cost_report(date_from, date_to):
line_ids = get_model("stock.grade.line").search([
["grade_id.date", ">=", date_from],
["grade_id.date", "<=", date_to],
["grade_id.state", "=", "done"]
])
total_amount = 0
total_qty = 0
for line in get_model("stock.grade.line").browse(line_ids):
total_amount += line.amount or 0
total_qty += line.qty or 0
avg_unit_cost = total_amount / total_qty if total_qty else 0
print(f"Cost Analysis Report")
print(f" Total Value: ${total_amount:,.2f}")
print(f" Total Quantity: {total_qty:,.2f}")
print(f" Average Unit Cost: ${avg_unit_cost:.2f}")
Version History¶
Last Updated: October 2025
Model Version: stock_grade_line.py
Framework: Netforce
Additional Resources¶
- Stock Grade Documentation:
stock.grade - Product Documentation:
product - Stock Location Documentation:
stock.location - Purchase Order Documentation:
purchase.order - Production Order Documentation:
production.order - Stock Lot Documentation:
stock.lot
This documentation is generated for developer onboarding and reference purposes.