← The Archive

Script to Compare GR and PO Orderlines Item

Script to Inspect Stock moves :

SELECT 
    po.name AS po_number,
    sp.name AS transfer_ref,
    spt.name AS operation_type,
    sl_src.complete_name AS source_location,
    sl_dest.complete_name AS dest_location,
    sl_dest.usage AS dest_usage,
    pt.name AS product_name,
    sm.product_uom_qty AS demand_qty,
    sm.quantity AS done_qty, -- in Odoo <=16 use: sm.quantity_done
    sm.to_refund AS is_to_refund,
    sm.state AS move_status
FROM stock_move sm
JOIN purchase_order_line pol ON pol.id = sm.purchase_line_id
JOIN purchase_order po ON po.id = pol.order_id
JOIN stock_picking sp ON sp.id = sm.picking_id
JOIN stock_picking_type spt ON spt.id = sp.picking_type_id
JOIN stock_location sl_src ON sl_src.id = sm.location_id
JOIN stock_location sl_dest ON sl_dest.id = sm.location_dest_id
JOIN product_product pp ON pp.id = sm.product_id
JOIN product_template pt ON pt.id = pp.product_tmpl_id
WHERE po.name = 'POTR/2606/02274'
ORDER BY sm.id;

Script Compare Receive vs Order Qty :

SELECT 
    po.name AS po_number,
    rp.name AS vendor,
    pt.name AS product_name,
    pol.product_qty AS qty_ordered,
    pol.qty_received AS qty_received,
    (pol.qty_received - pol.product_qty) AS excess_qty,
    po.state AS po_status
FROM purchase_order_line pol
JOIN purchase_order po ON po.id = pol.order_id
JOIN res_partner rp ON rp.id = po.partner_id
JOIN product_product pp ON pp.id = pol.product_id
JOIN product_template pt ON pt.id = pp.product_tmpl_id
WHERE pol.qty_received > pol.product_qty
ORDER BY po.name DESC;

Find PO Multiple "Done" Receipt :

SELECT 
    po.name AS po_number,
    COUNT(DISTINCT sp.id) AS validated_receipt_count,
    STRING_AGG(sp.name, ', ') AS receipt_numbers
FROM stock_picking sp
JOIN stock_picking_type spt ON spt.id = sp.picking_type_id
JOIN purchase_order po ON (sp.purchase_id = po.id OR sp.origin = po.name)
WHERE sp.state = 'done'
  AND spt.code = 'incoming'
GROUP BY po.name
HAVING COUNT(DISTINCT sp.id) > 1
ORDER BY validated_receipt_count DESC;

Script Net Receive qty (with returns):

WITH po_stock_summary AS (
    SELECT 
        pol.id AS pol_id,
        pol.order_id,
        pol.product_id,
        pol.product_qty AS qty_ordered,
        pol.qty_received AS odoo_recorded_received,
        
        -- Total Received (Vendor -> Internal Stock)
        COALESCE(SUM(CASE 
            WHEN sl_dest.usage = 'internal' AND sl_src.usage = 'supplier' 
            THEN sm.quantity -- in Odoo <= 16 use: sm.quantity_done
            ELSE 0 
        END), 0) AS actual_gr_qty,
        
        -- Total Returned to Vendor (Internal Stock -> Vendor)
        COALESCE(SUM(CASE 
            WHEN sl_dest.usage = 'supplier' AND sl_src.usage = 'internal' 
            THEN sm.quantity -- in Odoo <= 16 use: sm.quantity_done
            ELSE 0 
        END), 0) AS actual_return_qty

    FROM purchase_order_line pol
    JOIN stock_move sm ON sm.purchase_line_id = pol.id AND sm.state = 'done'
    JOIN stock_location sl_src ON sl_src.id = sm.location_id
    JOIN stock_location sl_dest ON sl_dest.id = sm.location_dest_id
    GROUP BY pol.id, pol.order_id, pol.product_id, pol.product_qty, pol.qty_received
)
SELECT 
    po.name AS po_number,
    rp.name AS vendor,
    pt.name AS product_name,
    s.qty_ordered,
    s.actual_gr_qty,
    s.actual_return_qty,
    (s.actual_gr_qty - s.actual_return_qty) AS net_received_qty,
    s.odoo_recorded_received,
    ((s.actual_gr_qty - s.actual_return_qty) - s.qty_ordered) AS real_excess_qty
FROM po_stock_summary s
JOIN purchase_order po ON po.id = s.order_id
JOIN res_partner rp ON rp.id = po.partner_id
JOIN product_product pp ON pp.id = s.product_id
JOIN product_template pt ON pt.id = pp.product_tmpl_id
WHERE s.actual_gr_qty > s.qty_ordered  -- True double GR
ORDER BY po.name DESC;
SQL Query
Created
August 20, 2026 at 03:19 PM
Last edited
August 26, 2026 at 10:46 AM