Product Transform Template Documentation¶
Overview¶
The Product Transform Template module provides standard recipes or bills of materials for stock transformation operations. Templates define repeatable input-output relationships for manufacturing processes, ensuring consistency and reducing manual data entry. This module works in conjunction with stock.transform to streamline production workflows.
Models Covered¶
This documentation covers two related models:
product.transform.template- Template header with metadataproduct.transform.template.lines- Template line items (inputs/outputs)
Product Transform Template¶
Model Information¶
Model Name: product.transform.template
Display Name: Product Transform Template
Key Fields: ["name"]
Export Name Field: name
Features¶
- ✅ Unique key constraint on name
- ✅ Layer type categorization
- ✅ State workflow
- ✅ Reusable transform recipes
- ❌ Audit logging
Understanding Key Fields¶
What are Key Fields?¶
For the product.transform.template model, the key field is:
This means the name must be unique. You cannot create two templates with the same name.
Uniqueness Guarantee:
# Valid - unique names:
Template 1: "Widget Assembly - Type A" ✅
Template 2: "Widget Assembly - Type B" ✅
# This would fail - duplicate key:
Template 3: "Widget Assembly - Type A" ❌ ERROR: Key already exists!
Database Implementation¶
The key field is enforced at the database level using a unique constraint.
State Workflow¶
| State | Description |
|---|---|
draft |
Template being created/edited |
approve |
Template approved for use in production |
Key Fields Reference¶
Header Fields¶
| Field | Type | Required | Description |
|---|---|---|---|
name |
Char | ✅ | Template name (unique key) |
seq |
Char | ❌ | Sequence/order number |
fg_product |
Many2One | ✅ | Finished goods product (product) |
target_output_qty |
Char | ❌ | Target output quantity |
state |
Selection | ✅ | Template state (draft/approve) |
layer_type |
Selection | ❌ | Layer type (BL/ML/UL/LAM/PRT) |
Layer Types¶
| Type | Code | Description |
|---|---|---|
| Back Layer | BL |
Back layer production |
| Middle Layer | ML |
Middle layer production |
| Upper Layer | UL |
Upper layer production |
| Laminating | LAM |
Laminating process |
| Printing | PRT |
Printing process |
Relationship Fields¶
| Field | Type | Description |
|---|---|---|
lines_in |
One2Many | Input product lines (type="in" or "balance") |
lines_out |
One2Many | Output product lines (type="out") |
API Methods¶
1. Create Template¶
Method: create(vals, context)
Creates a new transform template.
Parameters:
vals = {
"name": "Widget Assembly Standard", # Required: Unique name
"fg_product": 123, # Required: Final product
"target_output_qty": "100", # Optional: Target
"layer_type": "BL", # Optional: Layer type
"state": "draft", # Default: draft
"lines_in": [ # Input products
("create", {
"type": "in",
"product_id": 456,
"qty": 2,
"uom_id": 1,
"location_id": 789
})
],
"lines_out": [ # Output products
("create", {
"type": "out",
"product_id": 123,
"qty": 1,
"uom_id": 1,
"location_id": 789
})
]
}
Returns: int - New template ID
Example:
# Create standard assembly template
template_id = get_model("product.transform.template").create({
"name": "Laptop Assembly - Model X",
"fg_product": laptop_id,
"target_output_qty": "50",
"layer_type": "BL",
"lines_in": [
("create", {
"type": "in",
"product_id": motherboard_id,
"qty": 1,
"uom_id": unit_id,
"location_id": warehouse_id
}),
("create", {
"type": "in",
"product_id": screen_id,
"qty": 1,
"uom_id": unit_id,
"location_id": warehouse_id
})
],
"lines_out": [
("create", {
"type": "out",
"product_id": laptop_id,
"qty": 1,
"uom_id": unit_id,
"location_id": warehouse_id
})
]
})
2. Approve Template¶
Method: write(ids, vals, context)
Approves template for production use.
Example:
template = get_model("product.transform.template").browse(template_id)
template.write({"state": "approve"})
3. Search by Product¶
Method: search(condition)
Find templates by finished goods product.
Example:
# Find templates for specific product
templates = get_model("product.transform.template").search_browse([
["fg_product", "=", product_id]
])
for template in templates:
print(f"Template: {template.name}")
print(f" Inputs: {len(template.lines_in)}")
print(f" Outputs: {len(template.lines_out)}")
Related Models¶
| Model | Relationship | Description |
|---|---|---|
product.transform.template.lines |
One2Many | Template line items |
product |
Many2One | Finished goods product |
stock.transform |
Related | Transforms using this template |
Product Transform Template Lines¶
Model Information¶
Model Name: product.transform.template.lines
Display Name: Product Transform Template Lines
Key Fields: N/A
Features¶
- ✅ Multiple line types (in/out/service/balance)
- ✅ Dimensional specifications
- ✅ Cost tracking
- ❌ Audit logging
Line Types¶
| Type | Code | Description |
|---|---|---|
| From | in |
Input materials/products |
| To | out |
Output/finished products |
| Service | service |
Services (e.g., subcontracting) |
| Lot Balance | balance |
Remaining balance from lot |
Key Fields Reference¶
Basic Fields¶
| Field | Type | Required | Description |
|---|---|---|---|
pt_template_id |
Many2One | ✅ | Parent template (product.transform.template) |
type |
Selection | ✅ | Line type (in/out/service/balance) |
product_id |
Many2One | ✅ | Product (product, stock type) |
qty |
Decimal | ✅ | Quantity |
uom_id |
Many2One | ✅ | Unit of measure (uom) |
location_id |
Many2One | ✅ | Stock location (stock.location, internal) |
Cost Fields¶
| Field | Type | Description |
|---|---|---|
cost_price |
Decimal | Cost per unit (scale: 6) |
cost_amount |
Decimal | Total cost (computed: qty * cost_price) |
cost_group |
Selection | Cost grouping (balance/fixed_price/1-9) |
Quantity & Measurement Fields¶
| Field | Type | Description |
|---|---|---|
uom2_id |
Many2One | Secondary UoM |
qty2 |
Decimal | Secondary quantity |
planned_qty |
Decimal | Planned quantity |
thickness |
Decimal | Thickness in mm (scale: 2) |
width |
Decimal | Width in mm |
area_m2 |
Decimal | Area in square meters |
gross_wt |
Decimal | Gross weight |
net_wt |
Decimal | Net weight in kg |
weight |
Decimal | Gross weight (computed) |
Tracking Fields¶
| Field | Type | Description |
|---|---|---|
lot_id |
Many2One | Lot/Serial number (stock.lot) |
picking_id |
Many2One | Associated picking (stock.picking) |
supplier_id |
Many2One | Supplier (for service lines) (contact) |
move_id |
Many2One | Stock movement (stock.move) |
confirmed |
Boolean | Confirmation flag |
notes |
Text | Line notes |
Material Specification Fields¶
| Field | Type | Description |
|---|---|---|
master_prod_code |
Char | Master product code |
prod_code |
Char | Product code |
material_categ |
Many2One | Material category (material.categ) |
defect_categ |
Many2One | Defect category (defect.categ) |
color_code |
Char | Color code |
Stock Information¶
| Field | Type | Description |
|---|---|---|
qty_stock |
Decimal | Available stock (computed) |
timestamp |
DateTime | Timestamp |
API Methods¶
1. Create Template Line¶
Method: create(vals, context)
Creates a new template line.
Parameters:
vals = {
"pt_template_id": 123, # Required: Parent template
"type": "in", # Required: Line type
"product_id": 456, # Required: Product ID
"qty": 2, # Required: Quantity
"uom_id": 1, # Required: UoM
"location_id": 789, # Required: Location
"planned_qty": 2, # Optional: Planned qty
"thickness": 2.5, # Optional: Thickness (mm)
"width": 1200, # Optional: Width (mm)
"cost_price": 10.50 # Optional: Cost
}
Returns: int - New line ID
Example:
# Add input line to template
line_id = get_model("product.transform.template.lines").create({
"pt_template_id": template_id,
"type": "in",
"product_id": raw_material_id,
"qty": 100,
"uom_id": kg_uom_id,
"location_id": warehouse_loc_id
})
Computed Fields Functions¶
get_cost_price(ids, context)¶
Returns cost price from associated stock move.
get_amount_price(ids, context)¶
Calculates total cost amount (cost_price * qty).
get_gross_weight(ids, context)¶
Returns weight from lot or product.
get_qty_stock(ids, context)¶
Returns available stock for product/lot/location.
Related Models¶
| Model | Relationship | Description |
|---|---|---|
product.transform.template |
Many2One | Parent template |
product |
Many2One | Product referenced |
stock.lot |
Many2One | Lot/serial tracking |
stock.location |
Many2One | Storage location |
uom |
Many2One | Unit of measure |
stock.move |
Many2One | Associated stock movement |
contact |
Many2One | Supplier (for services) |
Common Use Cases¶
Use Case 1: Create Standard Recipe Template¶
# Define standard recipe for repeated use
template_id = get_model("product.transform.template").create({
"name": "Pizza Dough - Standard Recipe",
"fg_product": pizza_dough_id,
"target_output_qty": "10 kg",
"state": "draft",
"lines_in": [
("create", {
"type": "in",
"product_id": flour_id,
"qty": 6,
"uom_id": kg_id,
"location_id": warehouse_id
}),
("create", {
"type": "in",
"product_id": water_id,
"qty": 3.6,
"uom_id": liter_id,
"location_id": warehouse_id
}),
("create", {
"type": "in",
"product_id": yeast_id,
"qty": 0.2,
"uom_id": kg_id,
"location_id": warehouse_id
}),
("create", {
"type": "in",
"product_id": salt_id,
"qty": 0.2,
"uom_id": kg_id,
"location_id": warehouse_id
})
],
"lines_out": [
("create", {
"type": "out",
"product_id": pizza_dough_id,
"qty": 10,
"uom_id": kg_id,
"location_id": warehouse_id
})
]
})
# Approve template for use
template = get_model("product.transform.template").browse(template_id)
template.write({"state": "approve"})
Use Case 2: Use Template in Transform¶
# 1. Find template by product
templates = get_model("product.transform.template").search_browse([
["fg_product", "=", laptop_id],
["state", "=", "approve"]
])
if templates:
template_id = templates[0].id
# 2. Create transform using template
transform_id = get_model("stock.transform").create({
"date": "2025-10-27",
"final_product_code": laptop_id,
"pt_template": template_id
# Lines automatically loaded via onchange
})
# 3. Adjust quantities if needed (scale up 2x)
transform = get_model("stock.transform").browse(transform_id)
for line in transform.lines_in:
line.write({"qty": line.qty * 2})
for line in transform.lines_out:
line.write({"qty": line.qty * 2})
# 4. Validate
transform.validate()
Use Case 3: Create Template with Service Lines¶
# Template with outsourced services
template_id = get_model("product.transform.template").create({
"name": "Fabric Dyeing - Standard Process",
"fg_product": dyed_fabric_id,
"target_output_qty": "100 meters",
"lines_in": [
("create", {
"type": "in",
"product_id": raw_fabric_id,
"qty": 100,
"uom_id": meter_id,
"location_id": warehouse_id
})
],
"lines_service": [
("create", {
"type": "service",
"product_id": dyeing_service_id,
"qty": 100,
"uom_id": meter_id,
"supplier_id": dyeing_supplier_id,
"cost_price": 5.0 # Cost per meter
})
],
"lines_out": [
("create", {
"type": "out",
"product_id": dyed_fabric_id,
"qty": 98, # 2% loss
"uom_id": meter_id,
"location_id": warehouse_id
})
]
})
Use Case 4: Create Template with Dimensional Specs¶
# Template with material dimensions
template_id = get_model("product.transform.template").create({
"name": "Sheet Metal Cutting - 1200x2400",
"fg_product": cut_sheet_id,
"lines_in": [
("create", {
"type": "in",
"product_id": sheet_metal_id,
"qty": 1,
"uom_id": sheet_id,
"location_id": warehouse_id,
"width": 1200, # 1200mm
"thickness": 2.0, # 2.0mm
"area_m2": 2.88 # 2.88 m²
})
],
"lines_out": [
("create", {
"type": "out",
"product_id": cut_sheet_id,
"qty": 4,
"uom_id": piece_id,
"location_id": warehouse_id,
"width": 600, # 600mm each
"thickness": 2.0,
"area_m2": 0.72 # 0.72 m² each
})
]
})
Use Case 5: Clone and Modify Template¶
# Clone existing template and modify
original_template = get_model("product.transform.template").browse(template_id)
# Create new template with similar structure
new_template_id = get_model("product.transform.template").create({
"name": original_template.name + " - Modified",
"fg_product": original_template.fg_product.id,
"target_output_qty": original_template.target_output_qty,
"layer_type": original_template.layer_type,
"state": "draft"
})
# Copy and modify lines
for line in original_template.lines_in:
get_model("product.transform.template.lines").create({
"pt_template_id": new_template_id,
"type": line.type,
"product_id": line.product_id.id,
"qty": line.qty * 1.1, # 10% increase
"uom_id": line.uom_id.id,
"location_id": line.location_id.id
})
for line in original_template.lines_out:
get_model("product.transform.template.lines").create({
"pt_template_id": new_template_id,
"type": line.type,
"product_id": line.product_id.id,
"qty": line.qty * 1.1, # 10% increase
"uom_id": line.uom_id.id,
"location_id": line.location_id.id
})
Best Practices¶
1. Use Descriptive Template Names¶
# Good: Clear, descriptive names
"Laptop Assembly - Model X - Standard" ✅
"Pizza Dough - Commercial Recipe - 10kg Batch" ✅
"T-Shirt Printing - White - 50 units" ✅
# Bad: Vague names
"Template 1" ❌
"Assembly" ❌
"Recipe" ❌
2. Approve Templates After Testing¶
# Good workflow:
# 1. Create template in draft
template_id = get_model("product.transform.template").create({
"name": "New Assembly Process",
"state": "draft", # ✅ Start as draft
"fg_product": product_id,
"lines_in": [...],
"lines_out": [...]
})
# 2. Test with actual transform
test_transform_id = get_model("stock.transform").create({
"pt_template": template_id,
"date": "2025-10-27"
})
# Test, verify, adjust template if needed
# 3. Approve when ready
template = get_model("product.transform.template").browse(template_id)
template.write({"state": "approve"}) # ✅ Now ready for production
3. Organize by Layer Type¶
# Good: Use layer types for organization
templates = {
"BL": get_model("product.transform.template").search_browse([
["layer_type", "=", "BL"],
["state", "=", "approve"]
]),
"LAM": get_model("product.transform.template").search_browse([
["layer_type", "=", "LAM"],
["state", "=", "approve"]
])
} # ✅ Easy to find relevant templates
4. Include Planned Quantities¶
# Good: Set planned quantities for visibility
line_vals = {
"type": "in",
"product_id": product_id,
"qty": 100, # Standard quantity
"planned_qty": 100, # ✅ Expected/target quantity
"uom_id": uom_id,
"location_id": location_id
}
5. Version Your Templates¶
# Good: Include version in name
"Widget Assembly - v1.0"
"Widget Assembly - v1.1 - Reduced waste"
"Widget Assembly - v2.0 - New process" # ✅ Clear evolution
# Alternative: Use seq field
template.write({"seq": "v2.0"})
Search Functions¶
Search by Name¶
# Find template by name
templates = get_model("product.transform.template").search_browse([
["name", "=", "Laptop Assembly - Model X"]
])
Search by Product¶
# Find all templates for a product
templates = get_model("product.transform.template").search_browse([
["fg_product", "=", product_id]
])
Search by Layer Type¶
# Find all laminating templates
templates = get_model("product.transform.template").search_browse([
["layer_type", "=", "LAM"],
["state", "=", "approve"]
])
Search Approved Templates¶
# Find production-ready templates
templates = get_model("product.transform.template").search_browse([
["state", "=", "approve"]
])
Performance Tips¶
1. Cache Templates¶
# Efficient: Load and cache templates
template_cache = {}
def get_template_for_product(product_id):
if product_id not in template_cache:
templates = get_model("product.transform.template").search_browse([
["fg_product", "=", product_id],
["state", "=", "approve"]
])
template_cache[product_id] = templates[0] if templates else None
return template_cache[product_id]
# Use cached templates
template = get_template_for_product(laptop_id) # ✅ Single query
2. Load Lines with Template¶
# Efficient: Access lines through parent
template = get_model("product.transform.template").browse(template_id)
for line in template.lines_in: # ✅ Single query
print(line.product_id.name)
# Less efficient: Query lines separately
line_ids = get_model("product.transform.template.lines").search([
["pt_template_id", "=", template_id],
["type", "=", "in"]
])
for line_id in line_ids:
line = get_model("product.transform.template.lines").browse(line_id) # ❌ Multiple queries
Troubleshooting¶
"Key already exists" error¶
Cause: Template name already exists (duplicate)
Solution: Use unique template names; consider versioning scheme
Template not appearing in selection¶
Cause: State is "draft" or fg_product doesn't match
Solution: Approve template and verify fg_product field
Lines not copying to transform¶
Cause: onchange not triggered or template_id not set
Solution: Ensure pt_template field is set when creating transform
Cost calculations incorrect¶
Cause: Missing cost_price on template lines
Solution: Set cost_price on template lines or let system calculate
Testing Examples¶
Unit Test: Create Template¶
def test_create_template():
# Create template
template_id = get_model("product.transform.template").create({
"name": "Test Assembly",
"fg_product": finished_prod_id,
"lines_in": [
("create", {
"type": "in",
"product_id": part_a_id,
"qty": 2,
"uom_id": uom_id,
"location_id": loc_id
})
],
"lines_out": [
("create", {
"type": "out",
"product_id": finished_prod_id,
"qty": 1,
"uom_id": uom_id,
"location_id": loc_id
})
]
})
# Verify
template = get_model("product.transform.template").browse(template_id)
assert template.name == "Test Assembly"
assert len(template.lines_in) == 1
assert len(template.lines_out) == 1
assert template.state == "draft"
Unit Test: Template Usage in Transform¶
def test_template_usage():
# Create template
template_id = get_model("product.transform.template").create({
"name": "Test Assembly",
"fg_product": finished_prod_id,
"lines_in": [
("create", {
"type": "in",
"product_id": part_a_id,
"qty": 2,
"uom_id": uom_id,
"location_id": loc_id
})
],
"lines_out": [
("create", {
"type": "out",
"product_id": finished_prod_id,
"qty": 1,
"uom_id": uom_id,
"location_id": loc_id
})
]
})
# Create transform from template
transform_id = get_model("stock.transform").create({
"date": "2025-10-27",
"pt_template": template_id
})
# Verify lines loaded from template
transform = get_model("stock.transform").browse(transform_id)
assert len(transform.lines_in) == 1
assert len(transform.lines_out) == 1
assert transform.lines_in[0].product_id.id == part_a_id
assert transform.lines_in[0].qty == 2
Unit Test: Unique Name Constraint¶
def test_unique_name_constraint():
# Create first template
template1_id = get_model("product.transform.template").create({
"name": "Unique Template Name",
"fg_product": prod_id
})
# Try to create duplicate
try:
template2_id = get_model("product.transform.template").create({
"name": "Unique Template Name", # Same name
"fg_product": prod_id
})
assert False, "Should have raised exception"
except Exception as e:
assert "Key already exists" in str(e) or "unique" in str(e).lower()
Version History¶
Last Updated: 2025-10-27
Model Version: product_transform_template.py, product_transform_template_lines.py
Framework: Netforce
Additional Resources¶
- Stock Transform Documentation:
stock.transform - Stock Transform Line Documentation:
stock.transform.line - Product Documentation:
product - Stock Location Documentation:
stock.location
This documentation is generated for developer onboarding and reference purposes.