Skip to content

Stock Reports Documentation

Overview

This document provides comprehensive documentation for all stock reporting models in the Netforce stock management system. These transient models generate various analytical reports for inventory tracking, forecasting, aging analysis, and movement monitoring.


Report Models Summary

Report Model Purpose Key Features
report.stock.aging Stock aging analysis Tracks inventory age by periods
report.stock.card Stock card (ledger) Complete movement history
report.stock.expire Expiry tracking Monitors expiring lots by month
report.stock.forecast Inventory forecast Projects future stock levels
report.stock.invoice Invoice reconciliation Compares receipts vs invoices
report.stock.move Movement report Filtered stock movements
report.stock.plan Procurement planning Identifies reorder needs
report.stock.project Project tracking Stock by project
report.stock.summary Stock summary Opening/closing balances
report.forecast.details Detailed forecast Visual forecast charts
report.forecast.summary Forecast summary Multi-product forecast
report.master.product Master product report Variant aggregation
report.ship.cost Shipping costs Shipping expense tracking

1. Stock Aging Report

Model Information

Model Name: report.stock.aging
Display Name: Stock Aging Report
Type: Transient (temporary) model
Purpose: Analyzes how long inventory has been in stock

Fields Reference

Field Type Required Description
date Date Reference date for aging calculation
location_id Many2One Filter by location
product_id Many2One Filter by product
categ_id Many2One Filter by product category
period_days Integer Days per aging period (default: 30)
num_periods Integer Number of periods to show (default: 3)

Key Concepts

Aging Periods: Inventory is grouped into time periods showing how long it has been in stock: - Period 1: 0-30 days old - Period 2: 31-60 days old - Period 3: 61-90 days old - Older: 90+ days old

FIFO Assumption: Uses First-In-First-Out logic to allocate oldest stock first.

Report Data Structure

{
    "company_name": "Company Name",
    "location_name": "Warehouse A",
    "product_name": "Product XYZ",
    "date": "2024-01-31",
    "periods": [
        {
            "date_from": "2024-01-01",
            "date_to": "2024-01-30",
            "period_name": "0-29 days"
        },
        # ... more periods
    ],
    "lines": [
        {
            "product_id": 123,
            "product_name": "Product Name",
            "location_id": 456,
            "location_name": "Location Name",
            "cur_qty": 100.0,  # Current quantity
            "periods": [
                {"date_from": "...", "date_to": "...", "qty": 50.0},
                {"date_from": "...", "date_to": "...", "qty": 30.0}
            ],
            "older_qty": 20.0  # Quantity older than all periods
        }
    ],
    "older_date_to": "2023-12-31"
}

Usage Example

# Generate stock aging report
report_id = get_model("report.stock.aging").create({
    "date": "2024-01-31",
    "location_id": warehouse_id,
    "period_days": 30,
    "num_periods": 4
})

data = get_model("report.stock.aging").get_report_data([report_id])

Best Practices

  • Run monthly to identify slow-moving inventory
  • Use for obsolescence analysis
  • Compare across periods to track inventory turnover
  • Filter by category for specific product groups

2. Stock Card Report

Model Information

Model Name: report.stock.card
Display Name: Stock Card (Stock Ledger)
Type: Transient model
Purpose: Detailed movement history showing all ins/outs for products

Fields Reference

Field Type Required Description
date_from Date Start date
date_to Date End date
product_id Many2One Filter by product
categ_id Many2One Filter by category
location_id Many2One Filter by location
uom_id Many2One Unit of measure filter
lot_id Many2One Filter by lot/serial
invoice_id Many2One Filter by invoice
show_pending Boolean Include pending moves
show_qty2 Boolean Show secondary quantities
hide_zero Boolean Hide zero-quantity lines
hide_cost Boolean Hide cost information

Report Data Structure

{
    "company_name": "Company",
    "date_from": "2024-01-01",
    "date_to": "2024-01-31",
    "groups": [
        {
            "product_id": 123,
            "location_id": 456,
            "product_name": "Product Name",
            "product_code": "PROD001",
            "location_name": "Warehouse A",
            "location_code": "WH-A",
            "lines": [
                {
                    "date": "2024-01-15",
                    "bal_qty": 50.0,
                    "bal_cost_amount": 5000.0,
                    "bal_cost_price": 100.0
                },
                {
                    "id": 789,
                    "date": "2024-01-16",
                    "ref": "GR001",
                    "in_qty": 10.0,
                    "in_cost_price": 105.0,
                    "in_amount": 1050.0,
                    "bal_qty": 60.0,
                    "bal_cost_amount": 6050.0,
                    "bal_cost_price": 100.83
                }
            ],
            "total_in_qty": 10.0,
            "total_out_qty": 0.0,
            "total_in_amount": 1050.0,
            "total_out_amount": 0.0
        }
    ]
}

Usage Example

# Generate stock card for a product
report_id = get_model("report.stock.card").create({
    "date_from": "2024-01-01",
    "date_to": "2024-01-31",
    "product_id": product_id,
    "location_id": location_id,
    "show_pending": False
})

data = get_model("report.stock.card").get_report_data([report_id])

# Print movement summary
for group in data["groups"]:
    print(f"{group['product_code']}: In={group['total_in_qty']}, Out={group['total_out_qty']}")

Performance Considerations

  • Use specific filters to reduce query scope
  • For large datasets, limit date range
  • Query can be slow with many movements (uses caching in production)

3. Stock Expiry Report

Model Information

Model Name: report.stock.expire
Display Name: Stock Expiry Report
Type: Transient model
Purpose: Tracks expiring lots over upcoming months

Fields Reference

Field Type Required Description
date Date Starting date
forecast_days Integer Days to forecast (default: 180)
product_id Many2One Filter by product
location_id Many2One Filter by location

Report Data Structure

{
    "company_name": "Company",
    "date": "2024-01-01",
    "months": [
        {
            "month_name": "Jan 2024",
            "date_from": "2024-01-01",
            "date_to": "2024-01-31"
        },
        # ... 24 months
    ],
    "lines": [
        {
            "prod_id": 123,
            "prod_code": "PROD001",
            "prod_name": "Product Name",
            "months": [
                {
                    "exp_qty": 5,  # Number of lots expiring
                    "link_options": "{...}"  # For drill-down
                },
                # ... one entry per month
            ],
            "link_options": "{...}"
        }
    ]
}

Usage Example

# Generate expiry report
report_id = get_model("report.stock.expire").create({
    "date": "2024-01-01",
    "forecast_days": 365,
    "location_id": warehouse_id
})

data = get_model("report.stock.expire").get_report_data([report_id])

# Find products expiring in next 3 months
for line in data["lines"]:
    expiring_soon = sum(m["exp_qty"] for m in line["months"][:3])
    if expiring_soon > 0:
        print(f"{line['prod_code']}: {expiring_soon} lots expiring soon")

Best Practices

  • Run weekly to monitor upcoming expirations
  • Set up alerts for critical products
  • Use for FEFO (First-Expired-First-Out) planning
  • Coordinate with sales to prioritize expiring stock

4. Stock Forecast Report

Model Information

Model Name: report.stock.forecast
Display Name: Stock Forecast Report
Type: Transient model
Purpose: Projects future stock levels based on pending movements

Fields Reference

Field Type Required Description
date Date Starting date
location_id Many2One Location to forecast
categ_id Many2One Product category filter
product_id Many2One Specific product filter
period_days Integer Days per period (default: 7)
num_periods Integer Number of periods (default: 15)
show_lot Boolean Show by lot
show_location Boolean Show by location

Report Data Structure

{
    "company_name": "Company",
    "date": "2024-01-01",
    "period_days": 7,
    "periods": [
        {
            "date_from": "2024-01-01",
            "date_to": "2024-01-07",
            "period_name": "0-6 days"
        },
        # ... more periods
    ],
    "lines": [
        {
            "product_id": 123,
            "product_name": "Product",
            "code": "PROD001",
            "location_id": 456,
            "location_name": "Warehouse",
            "lot_id": 789,
            "lot_num": "LOT001",
            "qty": 100.0,  # Current quantity
            "periods": [
                {
                    "date_from": "2024-01-01",
                    "date_to": "2024-01-07",
                    "qty": 95.0,  # Projected quantity
                    "warning": False  # True if negative
                },
                # ... one per period
            ]
        }
    ]
}

Usage Example

# Generate 12-week forecast
report_id = get_model("report.stock.forecast").create({
    "date": "2024-01-01",
    "location_id": warehouse_id,
    "period_days": 7,
    "num_periods": 12
})

data = get_model("report.stock.forecast").get_report_data([report_id])

# Find products that will run out
for line in data["lines"]:
    for period in line["periods"]:
        if period["warning"]:  # Negative stock projected
            print(f"WARNING: {line['code']} will run out by {period['date_to']}")
            break

States Included in Forecast

  • done - Completed movements
  • pending - Planned movements
  • approved - Approved movements
  • forecast - Forecasted movements

5. Stock Invoice Report

Model Information

Model Name: report.stock.invoice
Display Name: Stock vs Invoice Report
Type: Transient model
Purpose: Reconciles goods received with supplier invoices

Fields Reference

Field Type Required Description
date_from Date Start date
date_to Date End date
product_id Many2One Filter by product
categ_id Many2One Filter by category
pick_id Many2One Specific goods receipt
products Many2Many Multiple products

Report Data Structure

{
    "date_from": "2024-01-01",
    "date_to": "2024-01-31",
    "lines": [
        {
            "prod_id": 123,
            "prod_code": "PROD001",
            "prod_name": "Product Name",
            "qty_received": 100.0,  # Goods receipt quantity
            "qty_returned": 5.0,    # Return quantity
            "qty_invoiced": 90.0,   # Invoiced quantity
            "qty_remain": 5.0       # Not yet invoiced (100-5-90)
        }
    ]
}

Usage Example

# Find uninvoiced receipts
report_id = get_model("report.stock.invoice").create({
    "date_from": "2024-01-01",
    "date_to": "2024-01-31"
})

data = get_model("report.stock.invoice").get_report_data([report_id])

# Print products with uninvoiced quantities
for line in data["lines"]:
    if line["qty_remain"] > 0:
        print(f"{line['prod_code']}: {line['qty_remain']} not invoiced")

Use Cases

  1. Invoice Matching: Verify all receipts have corresponding invoices
  2. Accrual Accounting: Identify goods received but not invoiced (GRN)
  3. Return Processing: Track returned goods vs credit notes
  4. Supplier Reconciliation: Match receipts with supplier statements

6. Stock Movement Report

Model Information

Model Name: report.stock.move
Display Name: Stock Movement Report
Type: Transient model
Purpose: Lists filtered stock movements by type

Fields Reference

Field Type Required Description
pick_type Selection in/internal/out
date_from Date Start date
date_to Date End date
location_from_id Many2One Source location
location_to_id Many2One Destination location
ref Char Reference filter
related_id Reference Related document
show_loss_only Boolean Show only loss movements

Pick Types

Type Description Use Case
in Goods Receipt Receiving from suppliers
internal Goods Transfer Internal movements
out Goods Issue Shipping to customers

Report Data Structure

{
    "title": "Goods Receive Report",
    "lines": [
        {
            "_item_no": 1,
            "number": "GR001",
            "date": "2024-01-15",
            "related": "PO-001",
            "product_code": "PROD001",
            "product_name": "Product Name",
            "location_from": "Supplier",
            "qty": 100.0,
            "uom": "PCS",
            "location_to": "Warehouse",
            "qty_loss": 2.0,  # Loss quantity if any
            "container_from": "CONT001",
            "lot": "LOT001",
            "state": "Completed",
            "ref": "GR001"
        }
    ]
}

Usage Example

# Get all goods receipts for a month
report_id = get_model("report.stock.move").create({
    "pick_type": "in",
    "date_from": "2024-01-01",
    "date_to": "2024-01-31"
}, context={"pick_type": "in"})

data = get_model("report.stock.move").get_report_data([report_id])

# Calculate total received
total_qty = sum(line["qty"] for line in data["lines"])
print(f"Total received: {total_qty}")

Loss Tracking

When show_loss_only=True, only movements with associated losses are shown. Losses are tracked by movements to inventory (loss) locations.


7. Stock Planning Report

Model Information

Model Name: report.stock.plan
Display Name: Stock Planning Report
Type: Transient model
Purpose: Identifies products needing reordering based on min/max levels

Fields Reference

Field Type Required Description
product_categ_id Many2One Filter by category
supplier_id Many2One Filter by supplier
plan_horizon Integer Planning days (default: 5)

Report Data Structure

{
    "company_name": "Company",
    "plan_horizon": 5,
    "lines": [
        {
            "product_id": 123,
            "product_code": "PROD001",
            "product_name": "Product Name",
            "plan_qty_horiz": 45.0,      # Projected quantity
            "min_qty": 50.0,              # Minimum stock level
            "req_qty": 5.0,               # Required quantity (min - projected)
            "stock_uom_name": "PCS",      # Stock UoM
            "order_qty": 50.0,            # Suggested order quantity
            "order_uom_name": "BOX",      # Order UoM
            "order_lead_time": 7,         # Lead time in days
            "supply_method": "Purchase",  # Purchase/Production
            "supplier_name": "Supplier A"
        }
    ]
}

Planning Logic

  1. Calculate projected stock at planning horizon date
  2. Compare with minimum stock levels
  3. Calculate required quantity to reach minimum
  4. Apply ordering constraints:
  5. Minimum order quantity
  6. Order quantity multiples
  7. Lead time considerations

Usage Example

# Generate procurement plan for next 10 days
report_id = get_model("report.stock.plan").create({
    "plan_horizon": 10,
    "product_categ_id": category_id
})

data = get_model("report.stock.plan").get_report_data([report_id])

# Create purchase orders for items below minimum
for line in data["lines"]:
    if line["supply_method"] == "Purchase" and line["order_qty"] > 0:
        print(f"Order {line['order_qty']} {line['order_uom_name']} of {line['product_code']}")
        print(f"  From: {line['supplier_name']}")
        print(f"  Lead time: {line['order_lead_time']} days")

Supply Methods

Method Description Used For
Purchase Buy from supplier Purchased items
Production Manufacture internally Manufactured items

8. Stock Project Report

Model Information

Model Name: report.stock.project
Display Name: Stock by Project Report
Type: Transient model
Purpose: Tracks stock movements by project/tracking category

Fields Reference

Field Type Required Description
date_from Date Start date
date_to Date End date
track_id Many2One Project/tracking category
contact_id Many2One Contact filter
number Char Document number filter
status Selection Delivered/Not Delivered

Report Data Structure

{
    "date_from": "2024-01-01",
    "date_to": "2024-01-31",
    "lines": [
        {
            "id": 123,
            "purchase_order_id": 456,
            "project_code": "PROJ-001",
            "project_name": "Project Alpha",
            "date": "2024-01-15",
            "number": "GR-001",
            "contact_code": "CUST001",
            "contact_name": "Customer Name",
            "related_to": "SO-001",
            "qty_in": 100.0,      # Received quantity
            "qty_out": 80.0,      # Issued quantity
            "state": "Delivered"  # Delivery status
        }
    ]
}

Delivery Status Logic

The system determines delivery status by comparing lot quantities: - Delivered: All received quantity has been issued - Not Delivered: Some received quantity remains in stock

Usage Example

# Get project stock movements
report_id = get_model("report.stock.project").create({
    "date_from": "2024-01-01",
    "date_to": "2024-01-31",
    "track_id": project_track_id,
    "status": "Not Delivered"
})

data = get_model("report.stock.project").get_report_data([report_id])

# Find pending deliveries
for line in data["lines"]:
    pending = line["qty_in"] - line["qty_out"]
    if pending > 0:
        print(f"{line['project_code']}: {pending} units pending delivery")

9. Stock Summary Report

Model Information

Model Name: report.stock.summary
Display Name: Stock Summary Report
Type: Transient model
Purpose: Opening/closing stock balances with period movements

Fields Reference

Field Type Required Description
date_from Date Period start
date_to Date Period end
location_id Many2One Location filter
product_id Many2One Product filter
master_product_id Many2One Master product (for variants)
lot_id Many2One Lot filter
container_id Many2One Container filter
prod_code Char Product code search
prod_categ_id Many2One Category filter
brand_id Many2One Brand filter
track_id Many2One Tracking filter
show_lot Boolean Show lot details
show_container Boolean Show container details
show_qty2 Boolean Show secondary qty
show_prod_desc Boolean Show descriptions
show_track Boolean Show tracking
show_area Boolean Show area calculations
show_image Boolean Include images
only_closing Boolean Only closing balances
hide_cost Boolean Hide cost columns
hide_zero_qty Boolean Hide zero quantities

Report Data Structure

{
    "company_name": "Company",
    "date_from": "2024-01-01",
    "date_to": "2024-01-31",
    "lines": [
        {
            "prod_id": 123,
            "prod_name": "Product",
            "prod_code": "PROD001",
            "prod_desc": "Description",
            "prod_img": "image.jpg",
            "lot_id": 456,
            "lot_num": "LOT001",
            "loc_id": 789,
            "loc_name": "Warehouse A",
            "uom_name": "PCS",

            # Opening balance
            "open_qty": 100.0,
            "open_amt": 10000.0,
            "open_qty2": 200.0,

            # Period movements
            "period_in_qty": 50.0,
            "period_in_amt": 5250.0,
            "period_in_qty2": 100.0,

            "period_out_qty": 30.0,
            "period_out_amt": 3000.0,
            "period_out_qty2": 60.0,

            # Closing balance
            "close_qty": 120.0,      # 100 + 50 - 30
            "close_amt": 12250.0,    # 10000 + 5250 - 3000
            "close_qty2": 240.0,     # 200 + 100 - 60

            # Optional fields
            "close_area": 240.0,     # If show_area=True
            "cont_name": "CONT001",  # If show_container=True
            "track_name": "PROJ-A"   # If show_track=True
        }
    ],
    "total_open_amt": 10000.0,
    "total_period_in_amt": 5250.0,
    "total_period_out_amt": 3000.0,
    "total_close_qty": 120.0,
    "total_close_amt": 12250.0
}

Performance Features

  • Redis Caching: Results cached for 5 minutes on production systems
  • Snapshot Support: Uses stock snapshots for faster opening balance calculation
  • Optimized Queries: Aggregates movements at database level

Usage Example

# Monthly stock summary
report_id = get_model("report.stock.summary").create({
    "date_from": "2024-01-01",
    "date_to": "2024-01-31",
    "location_id": warehouse_id,
    "show_lot": True,
    "hide_cost": False
})

data = get_model("report.stock.summary").get_report_data([report_id])

# Print summary
print(f"Opening Stock Value: {data['total_open_amt']}")
print(f"Receipts: +{data['total_period_in_amt']}")
print(f"Issues: -{data['total_period_out_amt']}")
print(f"Closing Stock Value: {data['total_close_amt']}")

Area Calculations

When show_area=True, calculates area/volume based on product dimensions:

Method 1 (calc_method=1):

area = width * ratio * close_qty

Method 2 (calc_method=2):

area = length * ratio * close_qty


10. Forecast Details Report

Model Information

Model Name: report.forecast.details
Display Name: Forecast Details Report
Type: Transient model
Purpose: Visual forecast chart for single product with matplotlib

Fields Reference

Field Type Required Description
date Date Forecast start date
product_id Many2One Product to forecast
forecast_days Integer Days ahead (default: 120)
show_shelf_life Boolean Show shelf life zones

Report Data Structure

{
    "company_name": "Company",
    "date": "2024-01-01",
    "product_code": "PROD001",
    "product_name": "Product Name",
    "svg_data": "<svg>...</svg>",           # SVG chart
    "svg_data_base64": "base64_encoded..."  # Base64 for embedding
}

Chart Features

  • Forecast Line: Projected quantity over time
  • Min/Max Lines: Reorder and maximum stock levels
  • Shortage Zones: Red shading when below minimum
  • Order Date: Vertical line showing when to order
  • Shelf Life Zones: 50% and 75% remaining shelf life (optional)

FEFO Logic

When show_shelf_life=True, tracks: - Life 50%: Lots with less than 50% shelf life remaining - Life 75%: Lots with less than 75% shelf life remaining

Usage Example

# Generate visual forecast
report_id = get_model("report.forecast.details").create({
    "date": "2024-01-01",
    "product_id": product_id,
    "forecast_days": 90,
    "show_shelf_life": True
})

data = get_model("report.forecast.details").get_report_data([report_id])

# Save chart to file
with open("forecast.svg", "w") as f:
    f.write(data["svg_data"])

Dependencies

Requires matplotlib library:

import matplotlib.pyplot as plt
import matplotlib.dates as mdates


11. Forecast Summary Report

Model Information

Model Name: report.forecast.summary
Display Name: Forecast Summary Report
Type: Transient model
Purpose: Multi-product forecast showing reorder recommendations

Fields Reference

Field Type Required Description
date Date Forecast start date
forecast_days Integer Days ahead (default: 180)
show_shelf_life Boolean Consider shelf life
order_only Boolean Show only products to order
product_id Many2One Single product filter

Report Data Structure

{
    "company_name": "Company",
    "date": "2024-01-01",
    "show_shelf_life": True,
    "order_only": False,
    "lines": [
        {
            "prod_id": 123,
            "prod_code": "PROD001",
            "prod_name": "Product Name",
            "current_qty": 100.0,        # Current stock
            "min_qty": 50.0,             # Minimum level
            "min_qty_50": 75.0,          # Min with 50% shelf life
            "min_qty_75": 100.0,         # Min with 75% shelf life
            "min_qty_date": "2024-02-15", # Date below minimum
            "min_qty_months": 1.5,       # Months until below min
            "lead_time": 14,             # Lead time days
            "order_date": "2024-02-01",  # Suggested order date
            "max_qty": 200.0,            # Maximum level
            "order_qty": 100.0,          # Suggested order qty
            "show_alert": True           # Order now!
        }
    ]
}

Alert Logic

show_alert=True when: - Projected stock will fall below minimum within forecast period - Order date is today or in the past - Considers shelf life constraints if enabled

Usage Example

# Generate urgent orders list
report_id = get_model("report.forecast.summary").create({
    "date": "2024-01-01",
    "forecast_days": 90,
    "show_shelf_life": True,
    "order_only": True  # Only show items needing orders
})

data = get_model("report.forecast.summary").get_report_data([report_id])

# Print purchase recommendations
for line in data["lines"]:
    if line["show_alert"]:
        print(f"URGENT: Order {line['order_qty']} of {line['prod_code']}")
        print(f"  Current: {line['current_qty']}")
        print(f"  Min: {line['min_qty']}")
        print(f"  Will run out: {line['min_qty_date']}")
        print(f"  Lead time: {line['lead_time']} days")

12. Master Product Report

Model Information

Model Name: report.master.product
Display Name: Master Product Report
Type: Transient model
Purpose: Aggregates stock data for product variants under master products

Fields Reference

Field Type Required Description
date_from Date Period start
date_to Date Period end
master_product_id Many2One Master product
location_id Many2One Location filter
width Float Width filter
length Float Length filter
sort_by Selection Sort order
hide_product_code Boolean Hide codes
hide_product_name Boolean Hide names
hide_qty1 Boolean Hide quantity
only_show_closing Boolean Only closing
hide_zero_qty Boolean Hide zeros
show_area Boolean Show area calcs

Sort Options

Option Description
date By receiving date
prod_code By product code
prod_name By product name
width By width dimension
length By length dimension
*_desc Descending variants

Report Data Structure

{
    "company_name": "Company",
    "date_from": "2024-01-01",
    "date_to": "2024-01-31",
    "master_products": [
        {
            "master_product_code": "MASTER-001",
            "master_product_name": "Master Product",
            "master_product_desc": "Description",
            "lines": [
                {
                    "prod_id": 123,
                    "prod_code": "VAR-001",
                    "prod_name": "Variant 1",
                    "width": 1.5,
                    "length": 2.0,
                    "in_date": "2024-01-15",
                    "lot_nums": "LOT001, LOT002",
                    "num_lots_open": 2,
                    "num_lots_close": 2,
                    "open_qty": 100.0,
                    "open_area": 300.0,
                    "period_in_qty": 50.0,
                    "in_area": 150.0,
                    "period_out_qty": 30.0,
                    "out_area": 90.0,
                    "close_qty": 120.0,
                    "close_area": 360.0,
                    "total_length_close": 240.0,
                    "pack_net_weight": 25.5
                }
            ],
            "total_open_qty": 100.0,
            "total_open_m2": 300.0,
            "total_lots_open": 2,
            "total_close_qty": 120.0,
            "total_close_area": 360.0,
            "total_close_lots": 2,
            "total_sum_length_close": 240.0
        }
    ]
}

Aggregation Logic

For each master product: 1. Find all variant products 2. Group movements by (product, location, date, width, length) 3. Sum quantities for same grouping 4. Calculate totals per master product 5. Apply sorting and filters

Usage Example

# Master product stock summary
report_id = get_model("report.master.product").create({
    "date_from": "2024-01-01",
    "date_to": "2024-01-31",
    "master_product_id": master_id,
    "location_id": warehouse_id,
    "show_area": True,
    "sort_by": "width",
    "hide_zero_qty": True
})

data = get_model("report.master.product").get_report_data([report_id])

# Print by master product
for master in data["master_products"]:
    print(f"\n{master['master_product_code']}: {master['master_product_name']}")
    print(f"  Total Closing: {master['total_close_qty']} units, {master['total_close_area']} m²")
    print(f"  Number of lots: {master['total_close_lots']}")

    for line in master["lines"]:
        print(f"    {line['width']}x{line['length']}: {line['close_qty']} units")

Length Calculations

For products with uom_name="CTN":

total_length = num_lots * length * ratio * 1000

For other UoMs:

total_length = num_lots * length


13. Ship Cost Report

Model Information

Model Name: report.ship.cost
Display Name: Shipping Cost Report
Type: Transient model
Purpose: Tracks shipping costs and methods

Fields Reference

Field Type Required Description
date_from Date Start date
date_to Date End date
contact_id Many2One Contact filter
ship_pay_by Selection Payment party
ship_method_id Many2One Shipping method

Payment Options

Option Description
company Paid by company
customer Paid by customer
supplier Paid by supplier

Report Data Structure

{
    "company_name": "Company",
    "date_from": "2024-01-01",
    "date_to": "2024-01-31",
    "lines": [
        {
            "id": 123,
            "date": "2024-01-15",
            "number": "GI-001",
            "contact": "Customer Name",
            "ship_method": "Express Courier",
            "ship_tracking": "TRACK123456",
            "ship_cost": 45.50,
            "ship_pay_by": "customer",
            "related": "SO-001"
        }
    ],
    "totals": {
        "ship_cost": 1250.75  # Sum of all shipping costs
    }
}

Usage Example

# Monthly shipping cost analysis
report_id = get_model("report.ship.cost").create({
    "date_from": "2024-01-01",
    "date_to": "2024-01-31",
    "ship_pay_by": "company"
})

data = get_model("report.ship.cost").get_report_data([report_id])

# Analyze by method
from collections import defaultdict
by_method = defaultdict(float)
for line in data["lines"]:
    by_method[line["ship_method"]] += line["ship_cost"]

print("Shipping Costs by Method:")
for method, cost in by_method.items():
    print(f"  {method}: ${cost:.2f}")

print(f"\nTotal: ${data['totals']['ship_cost']:.2f}")

Common Use Cases

1. Month-End Stock Valuation

# Generate stock summary for month-end
report_id = get_model("report.stock.summary").create({
    "date_from": "2024-01-01",
    "date_to": "2024-01-31",
    "only_closing": True,
    "hide_cost": False
})

data = get_model("report.stock.summary").get_report_data([report_id])

print(f"Closing Stock Value: ${data['total_close_amt']:.2f}")

2. Weekly Procurement Planning

# Generate weekly planning report
report_id = get_model("report.stock.plan").create({
    "plan_horizon": 7
})

data = get_model("report.stock.plan").get_report_data([report_id])

# Create purchase requisitions
for line in data["lines"]:
    if line["order_qty"] > 0:
        # Create PR or PO
        pass

3. Expiry Management

# Check expiring inventory
report_id = get_model("report.stock.expire").create({
    "date": date.today().strftime("%Y-%m-%d"),
    "forecast_days": 90
})

data = get_model("report.stock.expire").get_report_data([report_id])

# Alert for expiring lots
for line in data["lines"]:
    expiring_3mo = sum(m["exp_qty"] for m in line["months"][:3])
    if expiring_3mo > 5:
        print(f"ALERT: {line['prod_code']} has {expiring_3mo} lots expiring in 3 months")

4. Slow-Moving Analysis

# Generate aging report
report_id = get_model("report.stock.aging").create({
    "date": date.today().strftime("%Y-%m-%d"),
    "period_days": 90,
    "num_periods": 4
})

data = get_model("report.stock.aging").get_report_data([report_id])

# Find slow movers (>270 days old)
for line in data["lines"]:
    if line["older_qty"] > 0:
        print(f"SLOW MOVER: {line['product_code']} has {line['older_qty']} units >270 days old")

5. Product Movement Tracking

# Track specific product movements
report_id = get_model("report.stock.card").create({
    "date_from": "2024-01-01",
    "date_to": "2024-01-31",
    "product_id": product_id,
    "show_pending": True
})

data = get_model("report.stock.card").get_report_data([report_id])

for group in data["groups"]:
    print(f"\n{group['product_code']} at {group['location_name']}")
    print("Date       | In    | Out   | Balance")
    print("-" * 40)
    for line in group["lines"]:
        in_qty = line.get("in_qty", 0)
        out_qty = line.get("out_qty", 0)
        bal = line["bal_qty"]
        print(f"{line['date']} | {in_qty:5.0f} | {out_qty:5.0f} | {bal:7.0f}")

Performance Tips

1. Use Date Ranges Wisely

# BAD: No date filter
report_id = get_model("report.stock.card").create({
    "product_id": product_id
})

# GOOD: Specific date range
report_id = get_model("report.stock.card").create({
    "date_from": "2024-01-01",
    "date_to": "2024-01-31",
    "product_id": product_id
})

2. Filter Aggressively

# Combine filters to reduce result set
report_id = get_model("report.stock.summary").create({
    "date_from": "2024-01-01",
    "date_to": "2024-01-31",
    "location_id": warehouse_id,
    "prod_categ_id": category_id,
    "only_closing": True,
    "hide_zero_qty": True
})

3. Use Caching

Stock summary and master product reports use Redis caching: - Cache key includes all parameters - Cache expires after 5 minutes - Identical requests within 5 minutes return cached results

4. Leverage Snapshots

Stock snapshots dramatically improve performance:

# Without snapshot: calculates from all historical movements
# With snapshot: calculates from last snapshot forward

Create regular snapshots:

get_model("stock.snap").create_snap(date="2024-01-31")


Troubleshooting

"Report takes too long to run"

Causes: - Large movement history - No date filters - Missing snapshots

Solutions: - Add strict date ranges - Create stock snapshots - Use specific product/location filters - Enable caching (production only)

"Opening balance incorrect"

Causes: - Missing or outdated snapshot - Incorrect date_from selection

Solutions: - Create snapshot for period start - Verify date_from is correct - Check for backdated movements

"Forecast shows incorrect quantities"

Causes: - Pending movements not included - Incorrect stock movement states

Solutions: - Verify movement states (done/pending/approved/forecast) - Check date ranges on movements - Ensure forecast movements are properly created

"Master product report missing variants"

Causes: - Variants not linked to master - Products inactive - Zero quantities filtered out

Solutions: - Verify parent_id on variants - Check product active status - Disable hide_zero_qty filter


Best Practices

1. Regular Snapshot Creation

# Monthly snapshots
from dateutil.relativedelta import relativedelta

def create_monthly_snapshots():
    d = date.today()
    for i in range(12):
        snap_date = (d - relativedelta(months=i)).strftime("%Y-%m-01")
        get_model("stock.snap").create_snap(date=snap_date)

2. Scheduled Report Generation

# Daily aging report
def daily_aging_report():
    report_id = get_model("report.stock.aging").create({
        "date": date.today().strftime("%Y-%m-%d"),
        "period_days": 30,
        "num_periods": 4
    })

    data = get_model("report.stock.aging").get_report_data([report_id])

    # Email or save report
    send_aging_report(data)

3. Performance Monitoring

import time

start = time.time()
data = get_model("report.stock.summary").get_report_data([report_id])
elapsed = time.time() - start

if elapsed > 10:
    # Log slow report
    print(f"WARNING: Report took {elapsed:.2f} seconds")

4. Data Validation

# Validate stock summary balances
data = get_model("report.stock.summary").get_report_data([report_id])

for line in data["lines"]:
    # Verify formula
    expected_close = line["open_qty"] + line["period_in_qty"] - line["period_out_qty"]
    actual_close = line["close_qty"]

    if abs(expected_close - actual_close) > 0.01:
        print(f"ERROR: Balance mismatch for {line['prod_code']}")

Security Considerations

Permission Requirements

All reports respect standard Netforce permissions: - User must have read access to stock.move - User must have read access to products - Multi-company filtering applies automatically

Data Sensitivity

Cost information can be hidden:

report_id = get_model("report.stock.summary").create({
    "hide_cost": True,  # Hide cost columns
    # ... other params
})

Audit Logging

Report generation is not logged, but: - Underlying data access is audited if audit_log enabled - Cache hits/misses logged in production - Performance metrics logged to /tmp/report.log


API Reference

Common Parameters

Most reports share these parameters:

Parameter Type Common Default Description
date_from Date First of month Period start
date_to Date Last of month Period end
product_id Integer None Product filter
location_id Integer None Location filter

Common Methods

All report models implement:

def get_report_data(self, ids, context={}):
    """
    Generate report data.

    Args:
        ids: List of report record IDs
        context: Context dictionary

    Returns:
        Dictionary containing report data
    """

Helper Functions

# Get movement totals
totals = get_totals(
    date_from="2024-01-01",
    date_to="2024-01-31",
    product_id=123,
    location_id=456,
    states=["done", "pending"]
)

# Get aging periods
periods = get_periods(
    date="2024-01-31",
    period_days=30,
    num_periods=3
)

Version History

Date Version Changes
Oct 2024 1.0 Initial documentation


Last Updated: October 2024
Maintainer: Development Team