Skip to content

Stock Transform Documentation

Overview

The Stock Transform module (stock.transform) manages the conversion or transformation of materials and products. It handles manufacturing processes, assembly operations, disassembly, and any workflow where input materials are converted into output products. This is the core module for production and manufacturing in the system.


Model Information

Model Name: stock.transform
Display Name: Product Transform
Name Field: number
Key Fields: N/A

Features

  • ✅ Audit logging enabled (_audit_log = True)
  • ✅ Automated sequence numbering by layer type
  • ✅ State workflow management
  • ✅ Transform location management
  • ✅ Cost calculation and tracking
  • ✅ Template-based transforms
  • ✅ Lot assignment automation

State Workflow

draft → confirmed → done
voided
State Description
draft Initial state, can be edited
confirmed Stock moves created but not validated
done Completed, stock moves validated
voided Cancelled/voided

Layer Types

Type Code Description Sequence
Back Layer BL Back layer production layer_back
Middle Layer ML Middle layer production layer_middle
Upper Layer UL Upper layer production layer_upper
Laminating LAM Laminating process layer_laminating
Printing PRT Printing process layer_printing

Each layer type has its own sequence numbering.


Key Fields Reference

Header Fields

Field Type Required Description
number Char Auto-generated transform number (by layer type)
date Date Transform date
journal_id Many2One Stock journal (stock.journal)
final_product_code Many2One Final product code (product)
state Selection Current workflow state
notes Text Additional notes
ref Char External reference
project_id Many2One Associated project
date_done DateTime Completion timestamp
date_start DateTime Start timestamp
check_ratio Char Quality check ratio
job_order Many2One Job order reference (job.order)
pt_template Many2One Transform template (product.transform.template)
layer_type Selection Layer type for sequencing

Roller Fields (for printing/laminating)

Field Type Description
roller_no_1 Many2One Roller number 1 (color.code)
roller_no_2 Many2One Roller number 2 (color.code)
roller_no_3 Many2One Roller number 3 (color.code)
roller_no_4 Many2One Roller number 4 (color.code)

Computed Fields

Field Description
qty_in Total input quantity
qty_out Total output quantity
qty_diff Difference (output - input)
cost_total_in Total cost of input products
cost_total_out Total cost of output products
area_total_in Total area (M²) of inputs
area_total_out Total area (M²) of outputs

Relationship Fields

Field Type Description
lines One2Many All transform lines (stock.transform.line)
lines_in One2Many Input product lines (type="in" or "balance")
lines_out One2Many Output product lines (type="out")
lines_balance One2Many Balance/remainder lines (type="balance")
lines_service One2Many Service lines (type="service")
stock_moves One2Many Stock movements (stock.move)
purchase_orders One2Many Related purchase orders
related_id Reference Related document (purchase.order, stock.picking)
picking_id Many2One Associated picking
sale_id Many2One Associated sales order

API Methods

1. Create Transform

Method: create(vals, context)

Creates a new stock transform with automatic stock move creation.

Parameters:

vals = {
    "date": "2025-10-27",                 # Required: Transform date
    "layer_type": "BL",                    # Optional: For sequencing
    "final_product_code": 123,             # Optional: Final product
    "pt_template": 456,                    # Optional: Template to use
    "lines_in": [                          # Input products
        ("create", {
            "type": "in",
            "product_id": 789,
            "qty": 100,
            "uom_id": 1,
            "location_id": 201,
            "lot_id": 301
        })
    ],
    "lines_out": [                         # Output products
        ("create", {
            "type": "out",
            "product_id": 999,
            "qty": 95,
            "uom_id": 1,
            "location_id": 201
        })
    ]
}

context = {
    "layer_type": "BL",                    # For sequence generation
    "date": "2025-10-27"
}

Returns: int - New record ID

Behavior: - Generates sequence number based on layer_type - Checks for duplicate numbers - Creates stock moves immediately (in draft) - Links to job order if specified

Example:

# Create transform from raw material to finished product
transform_id = get_model("stock.transform").create({
    "date": "2025-10-27",
    "layer_type": "BL",
    "lines_in": [
        ("create", {
            "type": "in",
            "product_id": raw_mat_id,
            "qty": 1000,
            "uom_id": kg_uom_id,
            "location_id": warehouse_loc_id
        })
    ],
    "lines_out": [
        ("create", {
            "type": "out",
            "product_id": finished_prod_id,
            "qty": 950,
            "uom_id": kg_uom_id,
            "location_id": warehouse_loc_id
        })
    ]
}, context={"layer_type": "BL"})


2. Confirm Transform

Method: confirm(ids, context)

Creates stock movements through transform location.

Parameters: - ids (list): Transform IDs

Behavior: - Finds transform location (type="transform") - Creates "in" moves from source to transform location - Creates "out" moves from transform to destination - Sets moves to "approved" state (not done yet) - Sets transform state to "confirmed"

Example:

# Confirm transform to create stock moves
get_model("stock.transform").confirm([transform_id])

# Verify moves created
transform = get_model("stock.transform").browse(transform_id)
assert transform.state == "confirmed"
assert len(transform.stock_moves) > 0


3. Validate Transform

Method: validate(ids, context)

Confirms AND validates transform in one step.

Parameters: - ids (list): Transform IDs

Behavior: - Runs confirm logic if not already confirmed - Validates all stock moves (sets to "done") - Updates costs - Checks cost balance - Sets transform state to "done"

Example:

# Validate transform (creates and completes stock moves)
get_model("stock.transform").validate([transform_id])

# Verify completion
transform = get_model("stock.transform").browse(transform_id)
assert transform.state == "done"


4. Validate Manually

Method: validateManually(ids, context)

Validates existing stock moves without creating new ones.

Parameters: - ids (list): Transform IDs

Behavior: - Validates existing stock moves only - Updates costs - Checks cost balance

Example:

# Validate manually if moves already exist
get_model("stock.transform").validateManually([transform_id])


5. Void Transform

Method: void(ids, context)

Cancels transform and deletes stock movements.

Parameters: - ids (list): Transform IDs

Example:

# Void incorrect transform
get_model("stock.transform").void([transform_id])


6. Revert to Draft

Method: to_draft(ids, context)

Reverts transform to draft, deleting stock movements.

Parameters: - ids (list): Transform IDs

Example:

# Revert to draft to make changes
get_model("stock.transform").to_draft([transform_id])


7. Update Costs

Method: update_cost(ids, context)

Recalculates and updates product costs based on transform.

Behavior: - Computes cost for all input products - Distributes total input cost across output products - Updates move costs and product costs - Handles multiple outputs and cost groups

Example:

# Recalculate costs after changes
get_model("stock.transform").update_cost([transform_id])


8. Assign Lots

Method: assign_lots(ids, match_qty2=False, context)

Automatically assigns available lots to input lines.

Parameters: - ids (list): Transform IDs - match_qty2 (bool): Match by qty2 (weight) instead of qty

Behavior: - Finds available lots for each input product - Assigns lots based on product's lot selection strategy (FIFO/FEFO/qty) - Creates multiple lines if multiple lots needed - Respects min_life_remain_percent and max_lots_per_sale settings

Example:

# Auto-assign lots to input materials
get_model("stock.transform").assign_lots([transform_id])

# Or match by weight
get_model("stock.transform").assign_lots([transform_id], match_qty2=True)


9. Copy to Purchase Order

Method: copy_to_purchase(ids, context)

Creates purchase orders for service lines.

Parameters: - ids (list): Transform ID

Behavior: - Groups service lines by supplier - Creates purchase order for each supplier - Links PO to transform via related_id

Returns: Dictionary with flash message

Example:

# Create POs for outsourced services
result = get_model("stock.transform").copy_to_purchase([transform_id])
print(result["flash"])  # "Purchase order created successfully: PO-001, PO-002"


10. Update Lot Balance

Method: update_lot_balance(ids, context)

Creates balance lines for remaining lot quantities.

Parameters: - ids (list): Transform IDs

Behavior: - Checks products with pt_balance_remove=True - Calculates remaining quantity in lot after transform - Creates balance lines for remainder

Example:

# Update lot balance tracking
get_model("stock.transform").update_lot_balance([transform_id])


UI Events (onchange methods)

onchange_layertype

Triggered when layer type changes. Updates: - Number field with new sequence based on layer type

onchange_product

Triggered when final_product_code changes. Updates: - pt_template if matching template found - Loads template lines into lines_in and lines_out

onchange_PT_template

Triggered when template selected. Updates: - lines_in from template - lines_out from template - Copies planned quantities, dimensions, locations


Computed Fields Functions

get_total_qty(ids, context)

Calculates: - qty_in: Sum of all input line quantities - qty_out: Sum of all output line quantities - qty_diff: qty_out - qty_in

get_total_cost(ids, context)

Calculates: - cost_total_in: Sum of input line costs - cost_total_out: Sum of output line costs - area_total_in: Sum of input areas - area_total_out: Sum of output areas


Configuration Settings

Required Settings

Setting Location Description
transform_journal_id settings (ID:1) Journal for transform transactions

Required System Data

  • Transform location must exist (type="transform")
  • Sequence for each layer type (layer_back, layer_middle, etc.)

Model Relationship Description
stock.transform.line One2Many Transform line items
product Many2One Input/output products
stock.lot Many2One Lot tracking
stock.location Many2One Storage locations
stock.move One2Many Stock movements
stock.journal Many2One Transaction journal
uom Many2One Units of measure
purchase.order One2Many Service purchase orders
project Many2One Project allocation
job.order Many2One Job order reference
product.transform.template Many2One Transform template/recipe

Common Use Cases

Use Case 1: Simple Material Transform

# Convert raw material to finished product
transform_id = get_model("stock.transform").create({
    "date": "2025-10-27",
    "lines_in": [
        ("create", {
            "type": "in",
            "product_id": raw_material_id,
            "qty": 100,
            "uom_id": kg_uom_id,
            "location_id": warehouse_loc_id
        })
    ],
    "lines_out": [
        ("create", {
            "type": "out",
            "product_id": finished_product_id,
            "qty": 95,  # 5% waste
            "uom_id": kg_uom_id,
            "location_id": warehouse_loc_id
        })
    ]
})

# Validate to complete transform
get_model("stock.transform").validate([transform_id])

Use Case 2: Multi-Input Assembly

# Assemble product from multiple components
transform_id = get_model("stock.transform").create({
    "date": "2025-10-27",
    "final_product_code": laptop_id,
    "lines_in": [
        ("create", {
            "type": "in",
            "product_id": motherboard_id,
            "qty": 1,
            "uom_id": unit_uom_id,
            "location_id": warehouse_loc_id
        }),
        ("create", {
            "type": "in",
            "product_id": screen_id,
            "qty": 1,
            "uom_id": unit_uom_id,
            "location_id": warehouse_loc_id
        }),
        ("create", {
            "type": "in",
            "product_id": battery_id,
            "qty": 1,
            "uom_id": unit_uom_id,
            "location_id": warehouse_loc_id
        })
    ],
    "lines_out": [
        ("create", {
            "type": "out",
            "product_id": laptop_id,
            "qty": 1,
            "uom_id": unit_uom_id,
            "location_id": warehouse_loc_id
        })
    ]
})

get_model("stock.transform").validate([transform_id])

Use Case 3: Transform with Template

# Use template for standard transform
# 1. Find template
template = get_model("product.transform.template").search_browse([
    ["fg_product", "=", widget_id]
])[0]

# 2. Create transform from template
transform_id = get_model("stock.transform").create({
    "date": "2025-10-27",
    "final_product_code": widget_id,
    "pt_template": template.id
    # Lines automatically loaded from template via onchange
})

# 3. Validate
get_model("stock.transform").validate([transform_id])

Best Practices

1. Use Templates for Repeated Transforms

# Good: Create template for standard process
template_id = get_model("product.transform.template").create({
    "name": "Widget Assembly Standard",
    "fg_product": widget_id,
    "lines_in": [...],
    "lines_out": [...]
})

# Then create transforms from template
transform_id = get_model("stock.transform").create({
    "date": "2025-10-27",
    "pt_template": template_id
})  # ✅ Consistent, fast

2. Always Balance Input/Output Costs

# Good: Let system calculate output costs
transform_id = get_model("stock.transform").create({
    "lines_in": [
        ("create", {"product_id": p1, "qty": 10, "cost_price": 5})  # Cost: 50
    ],
    "lines_out": [
        ("create", {"product_id": p2, "qty": 8})  # Cost auto-calculated: 6.25
    ]
})
get_model("stock.transform").validate([transform_id])
get_model("stock.transform").update_cost([transform_id])  # ✅ Balanced

3. Use Layer Types for Organized Numbering

# Good: Specify layer type for clear sequencing
transform_id = get_model("stock.transform").create({
    "date": "2025-10-27",
    "layer_type": "BL",  # ✅ Gets BL-2025-001
    # ...
}, context={"layer_type": "BL"})

Troubleshooting

"Missing transform location"

Cause: No location with type="transform" exists
Solution: Create transform location in system

"Missing transform journal"

Cause: transform_journal_id not configured in settings
Solution: Configure transform journal in inventory settings

"Can not delete product transforms in this status"

Cause: Trying to delete completed transform
Solution: Only delete draft transforms; void or revert to draft first

"{number} number already exist in system"

Cause: Duplicate transform number
Solution: System should auto-increment; check sequence configuration

"From products cost different than to products cost"

Cause: Input and output costs don't match
Solution: Run update_cost to balance, or adjust product costs


Version History

Last Updated: 2025-10-27
Model Version: stock_transform.py
Framework: Netforce


Additional Resources

  • Stock Transform Line Documentation: stock.transform.line
  • Product Transform Template Documentation: product.transform.template
  • Stock Move Documentation: stock.move
  • Product Documentation: product

This documentation is generated for developer onboarding and reference purposes.