Skip to content

Product Borrow Line Documentation

Overview

The Product Borrow Line module (product.borrow.line) represents individual items within a borrow request. Each line tracks a specific product, requested quantity, and automatically calculates issued and returned quantities based on stock movements.


Model Information

Model Name: product.borrow.line
Display Name: Borrow Line
Name Field: number (inherited)
Key Fields: N/A

Features

  • ✅ Automatic quantity tracking (issued/returned)
  • ✅ Lot/Serial number support
  • ❌ Audit logging
  • ❌ Cascade deletion with parent

Key Fields Reference

Line Fields

Field Type Required Description
request_id Many2One Parent borrow request (product.borrow)
product_id Many2One Product being borrowed (product)
qty Decimal Requested quantity
uom_id Many2One Unit of measure (uom)
lot_id Many2One Lot/Serial number (stock.lot)

Computed Fields

Field Description
issued_qty Total quantity issued to employee (from outbound moves)
returned_qty Total quantity returned from employee (from inbound moves)

API Methods

1. Create Borrow Line

Method: create(vals, context)

Creates a new borrow line item.

Parameters:

vals = {
    "request_id": 123,                    # Required: Parent borrow request
    "product_id": 456,                    # Required: Product ID
    "qty": 2,                             # Required: Quantity
    "uom_id": 1,                          # Required: UoM ID
    "lot_id": 789                         # Optional: Lot/serial number
}

Returns: int - New line ID

Example:

# Add line to existing borrow request
line_id = get_model("product.borrow.line").create({
    "request_id": borrow_id,
    "product_id": product_id,
    "qty": 1,
    "uom_id": unit_id,
    "lot_id": serial_id
})


2. Update Line

Method: write(ids, vals, context)

Updates borrow line data.

Example:

line = get_model("product.borrow.line").browse(line_id)

# Update quantity
line.write({"qty": 3})

# Update lot
line.write({"lot_id": new_lot_id})


3. Delete Line

Method: delete(ids, context)

Deletes a borrow line.

Example:

get_model("product.borrow.line").delete([line_id])


Computed Fields Functions

get_qty_issued(ids, context)

Calculates total issued quantity from stock movements.

Behavior: - Sums quantities from outbound stock moves (type="out") - Only counts moves in "done" state - Links via related_id to parent borrow request

Example:

line = get_model("product.borrow.line").browse(line_id)

print(f"Requested: {line.qty}")
print(f"Issued: {line.issued_qty}")
print(f"Pending: {line.qty - line.issued_qty}")


get_qty_returned(ids, context)

Calculates total returned quantity from stock movements.

Behavior: - Sums quantities from inbound stock moves (type="in") - Only counts moves in "done" state - Links via related_id to parent borrow request

Example:

line = get_model("product.borrow.line").browse(line_id)

print(f"Issued: {line.issued_qty}")
print(f"Returned: {line.returned_qty}")
print(f"Outstanding: {line.issued_qty - line.returned_qty}")


Model Relationship Description
product.borrow Many2One Parent borrow request
product Many2One Product being borrowed
uom Many2One Unit of measure
stock.lot Many2One Lot/serial tracking
stock.move Related Stock movements (via parent)

Common Use Cases

Use Case 1: Add Item to Borrow Request

# Add product to existing borrow request
line_id = get_model("product.borrow.line").create({
    "request_id": borrow_id,
    "product_id": laptop_id,
    "qty": 1,
    "uom_id": unit_id,
    "lot_id": serial_number_id
})

print(f"Line created: {line_id}")

Use Case 2: Track Issue and Return Status

# Check status of borrowed item
line = get_model("product.borrow.line").browse(line_id)

print(f"Product: {line.product_id.name}")
print(f"Requested: {line.qty}")
print(f"Issued: {line.issued_qty}")
print(f"Returned: {line.returned_qty}")

outstanding = line.issued_qty - line.returned_qty
if outstanding > 0:
    print(f"⚠️ Outstanding: {outstanding} units")
else:
    print("✅ Fully returned")

Use Case 3: Find Outstanding Items by Employee

# Find all items not returned by employee
employee_id = 123

borrows = get_model("product.borrow").search_browse([
    ["employee_id", "=", employee_id],
    ["state", "=", "approved"]
])

for borrow in borrows:
    for line in borrow.lines:
        outstanding = line.issued_qty - line.returned_qty
        if outstanding > 0:
            print(f"Borrow: {borrow.number}")
            print(f"  Product: {line.product_id.name}")
            print(f"  Outstanding: {outstanding}")
            print(f"  Due: {borrow.due_date}")

Use Case 4: Partial Return Tracking

# Monitor partial returns
line = get_model("product.borrow.line").browse(line_id)

total_issued = line.issued_qty
total_returned = line.returned_qty
percent_returned = (total_returned / total_issued * 100) if total_issued > 0 else 0

print(f"Return Progress: {percent_returned:.0f}%")
print(f"Issued: {total_issued}, Returned: {total_returned}")

if percent_returned < 100:
    print(f"Still waiting for: {total_issued - total_returned} units")

Best Practices

1. Always Specify Lot for Serialized Items

# Good: Track high-value items by serial
line_vals = {
    "request_id": borrow_id,
    "product_id": laptop_id,
    "qty": 1,
    "uom_id": unit_id,
    "lot_id": serial_number_id  # ✅ Can identify exact unit
}

# Bad: No serial tracking for expensive item
line_vals = {
    "request_id": borrow_id,
    "product_id": laptop_id,
    "qty": 1,
    "uom_id": unit_id
    # ❌ Cannot identify which laptop
}

2. Check Outstanding Before Closing

# Good: Verify all items returned before completing
borrow = get_model("product.borrow").browse(borrow_id)

all_returned = True
for line in borrow.lines:
    if line.issued_qty - line.returned_qty > 0:
        all_returned = False
        print(f"⚠️ {line.product_id.name} not fully returned")

if all_returned:
    borrow.set_done()  # ✅ Safe to complete
else:
    print("❌ Cannot complete - items still outstanding")

# Bad: Complete without checking
borrow.set_done()  # ❌ May have outstanding items

3. Use Computed Fields, Don't Store

# Good: Use computed fields
line = get_model("product.borrow.line").browse(line_id)
outstanding = line.issued_qty - line.returned_qty  # ✅ Always accurate

# Bad: Store calculated values
line.write({"outstanding_qty": outstanding})  # ❌ Can become stale

Search Functions

Search by Product

# Find all borrow lines for a specific product
condition = [["product_id", "=", product_id]]
lines = get_model("product.borrow.line").search(condition)

Search by Borrow Request

# Find lines for a specific borrow request
condition = [["request_id", "=", borrow_id]]
lines = get_model("product.borrow.line").search(condition)

Search by Lot

# Find lines for a specific serial number
condition = [["lot_id", "=", lot_id]]
lines = get_model("product.borrow.line").search(condition)

Troubleshooting

Issued/Returned quantities not updating

Cause: Stock moves not in "done" state or missing related_id link
Solution: Ensure pickings are validated and properly linked to borrow request

Computed fields showing None

Cause: No stock moves exist yet
Solution: This is normal before issuing items; values populate after validation

Outstanding quantity incorrect

Cause: UoM mismatch in stock moves
Solution: Ensure consistent UoM across borrow lines and stock moves


Performance Tips

1. Access Lines Through Parent

# Efficient: Load borrow with lines
borrow = get_model("product.borrow").browse(borrow_id)
for line in borrow.lines:  # ✅ Single query
    print(line.product_id.name)

# Less efficient: Query each line
line_ids = get_model("product.borrow.line").search([
    ["request_id", "=", borrow_id]
])
for line_id in line_ids:
    line = get_model("product.borrow.line").browse(line_id)  # ❌ Multiple queries

2. Computed Fields Recalculate

# Computed fields recalculate on each access
line = get_model("product.borrow.line").browse(line_id)

# Cache if using multiple times
issued = line.issued_qty
returned = line.returned_qty
outstanding = issued - returned  # ✅ Use cached values

# Bad: Multiple accesses
if line.issued_qty > line.returned_qty:  # Computes twice
    print(f"{line.issued_qty - line.returned_qty}")  # Computes twice more

Testing Examples

Unit Test: Create Line

def test_create_borrow_line():
    # Create parent borrow
    borrow_id = get_model("product.borrow").create({
        "date": "2025-10-27",
        "employee_id": emp_id,
        "borrow_for": "Test",
        "due_date": "2025-11-10"
    })

    # Create line
    line_id = get_model("product.borrow.line").create({
        "request_id": borrow_id,
        "product_id": prod_id,
        "qty": 5,
        "uom_id": uom_id
    })

    # Verify
    line = get_model("product.borrow.line").browse(line_id)
    assert line.request_id.id == borrow_id
    assert line.qty == 5
    assert line.issued_qty == 0  # Not issued yet
    assert line.returned_qty == 0

Unit Test: Track Issued Quantity

def test_issued_quantity():
    # Create borrow with line
    borrow_id = get_model("product.borrow").create({
        "date": "2025-10-27",
        "employee_id": emp_id,
        "borrow_for": "Test",
        "due_date": "2025-11-10",
        "lines": [("create", {
            "product_id": prod_id,
            "qty": 10,
            "uom_id": uom_id
        })]
    })

    borrow = get_model("product.borrow").browse(borrow_id)
    borrow.approve()

    # Issue items
    result = borrow.copy_to_picking()
    pick = get_model("stock.picking").browse(result["picking_id"])
    pick.set_done()

    # Verify issued quantity updated
    line = borrow.lines[0]
    assert line.issued_qty == 10
    assert line.returned_qty == 0

Unit Test: Track Returned Quantity

def test_returned_quantity():
    # Setup: Create and issue borrow
    borrow_id = setup_borrow_and_issue(prod_id, 10)

    # Return 6 items
    borrow = get_model("product.borrow").browse(borrow_id)
    result = borrow.copy_to_pick_in()
    return_pick = get_model("stock.picking").browse(result["picking_id"])
    return_pick.lines[0].write({"qty": 6})
    return_pick.set_done()

    # Verify
    line = borrow.lines[0]
    assert line.issued_qty == 10
    assert line.returned_qty == 6
    assert line.issued_qty - line.returned_qty == 4  # Outstanding

Version History

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


Additional Resources

  • Product Borrow Documentation: product.borrow
  • Product Documentation: product
  • Stock Move Documentation: stock.move
  • Stock Lot Documentation: stock.lot

This documentation is generated for developer onboarding and reference purposes.