-- FUNCTION: trans.apply_bulk_stock_movements(jsonb)

-- DROP FUNCTION IF EXISTS trans.apply_bulk_stock_movements(jsonb);

CREATE OR REPLACE FUNCTION trans.apply_bulk_stock_movements(
	p_movements jsonb)
    RETURNS jsonb
    LANGUAGE 'plpgsql'
    COST 100
    VOLATILE PARALLEL UNSAFE
AS $BODY$
DECLARE
    v_movement JSONB;
    v_results JSONB := '[]'::JSONB;
    v_result JSONB;
    v_success BOOLEAN := TRUE;
    v_error_msg TEXT;
BEGIN
    -- Process each movement in the array
    FOR v_movement IN SELECT * FROM jsonb_array_elements(p_movements)
    LOOP
        BEGIN
            -- Apply individual stock movement using the 9-parameter function
            PERFORM trans.apply_stock_movement(
                (v_movement->>'merchant_id')::TEXT,
                (v_movement->>'warehouse_id')::TEXT,
                (v_movement->>'sku')::TEXT,
                (v_movement->>'batch_no')::TEXT,
				(v_movement->>'catalogue_id')::TEXT,
				(v_movement->>'expiry_date')::DATE,
				(v_movement->>'uom')::TEXT,
                (v_movement->>'qty')::NUMERIC,
                (v_movement->>'txn_type')::TEXT,
                (v_movement->>'ref_type')::TEXT,
                (v_movement->>'ref_id')::UUID,
                (v_movement->>'created_by')::TEXT,
                (v_movement->>'ref_no')::TEXT,
                (v_movement->>'remarks')::TEXT
            );
            
            -- Build success result object
            v_result := jsonb_build_object(
                'ledger_id', gen_random_uuid(),
                'sku', v_movement->>'sku',
                'qty', v_movement->>'qty',
                'success', true,
                'error', null
            );
            
        EXCEPTION WHEN OTHERS THEN
            -- Handle individual movement errors
            v_success := FALSE;
            v_error_msg := SQLERRM;
            
            v_result := jsonb_build_object(
                'ledger_id', null,
                'sku', v_movement->>'sku',
                'qty', v_movement->>'qty',
                'success', false,
                'error', v_error_msg
            );
        END;
        
        -- Add to results array
        v_results := v_results || v_result;
    END LOOP;
    
    -- If any movement failed, rollback the entire transaction
    IF NOT v_success THEN
        RAISE EXCEPTION 'One or more stock movements failed. Transaction rolled back.';
    END IF;
    
    RETURN v_results;
END;
$BODY$;

ALTER FUNCTION trans.apply_bulk_stock_movements(jsonb)
    OWNER TO trans_owner;
-- FUNCTION: trans.apply_stock_movement(text, text, text, text, text, date, text, numeric, text, text, uuid, text, text, text)

DROP FUNCTION IF EXISTS trans.apply_stock_movement(text, text, text, text, text, date, text, numeric, text, text, uuid, text);
DROP FUNCTION IF EXISTS trans.apply_stock_movement(text, text, text, text, text, date, text, numeric, text, text, uuid, text, text, text);

CREATE OR REPLACE FUNCTION trans.apply_stock_movement(
	p_merchant_id text,
	p_warehouse_id text,
	p_sku text,
	p_batch_no text,
	p_catalogue_id text,
	p_expiry_date date,
	p_uom text,
	p_qty numeric,
	p_txn_type text,
	p_ref_type text,
	p_ref_id uuid,
	p_user text,
    p_ref_no text DEFAULT NULL,
    p_remarks text DEFAULT NULL)
    RETURNS void
    LANGUAGE 'plpgsql'
    COST 100
    VOLATILE PARALLEL UNSAFE
AS $BODY$
DECLARE
    v_ledger_id uuid := gen_random_uuid();
    v_qty_change numeric;
BEGIN
    ----------------------------------------------------------------
    -- Idempotency check
    ----------------------------------------------------------------
    IF EXISTS (
        SELECT 1
        FROM trans.scm_stock_ledger
        WHERE ref_type = p_ref_type
          AND ref_id   = p_ref_id
          AND sku      = p_sku
          AND batch_no = p_batch_no
    ) THEN
        RETURN;
    END IF;

    ----------------------------------------------------------------
    -- Determine quantity change based on transaction type
    ----------------------------------------------------------------
    CASE p_txn_type
        WHEN 'GRN_IN' THEN
            v_qty_change := p_qty;  -- Add stock
        WHEN 'ADJUST_IN' THEN
            v_qty_change := p_qty;  -- Add stock
        WHEN 'RETURN_IN' THEN
            v_qty_change := p_qty;  -- Add stock
        WHEN 'TRANSFER_IN' THEN
            v_qty_change := p_qty;  -- Add stock
        WHEN 'ADJUST_OUT' THEN
            v_qty_change := -p_qty; -- Subtract stock (negate the positive qty)
        WHEN 'SALE_OUT' THEN
            v_qty_change := -p_qty; -- Subtract stock
        WHEN 'TRANSFER_OUT' THEN
            v_qty_change := -p_qty; -- Subtract stock
        ELSE
            RAISE EXCEPTION 'Unknown transaction type: %', p_txn_type;
    END CASE;

	Raise Notice 'in';
    RAISE NOTICE 'Transaction type: %, Input qty: %, Calculated change: %', 
        p_txn_type, p_qty, v_qty_change;
    
    RAISE NOTICE 'Inserting ledger: %, %, %, %, %, %, %, %, %, %, %',
        v_ledger_id,
        p_merchant_id,
        p_warehouse_id,
        p_sku,
        p_batch_no,
        p_txn_type,
        v_qty_change,  -- Store the calculated change in ledger
        p_ref_type,
        p_ref_id,
        p_user,
        NOW();
        
    ----------------------------------------------------------------
    -- Ledger insert (store the ACTUAL qty change with proper sign)
    ----------------------------------------------------------------
    INSERT INTO trans.scm_stock_ledger (
        ledger_id,
        merchant_id,
        warehouse_id,
        catalogue_id,
        sku,
        batch_no,
        txn_type,
        qty,
        uom,
        ref_type,
        ref_id,
        ref_no,
        remarks,
        created_by,
        created_at
    ) VALUES (
        v_ledger_id,
        p_merchant_id,
        p_warehouse_id,
        p_catalogue_id,
        p_sku,
        p_batch_no,
        p_txn_type,
        v_qty_change,  -- Store with correct sign
        p_uom,
        p_ref_type,
        p_ref_id,
        p_ref_no,
        p_remarks,
        p_user,
        NOW()
    );
    
    RAISE NOTICE 'Stock ledger done';
    
    ----------------------------------------------------------------
    -- Stock snapshot upsert (ALL TABLE COLUMNS)
    ----------------------------------------------------------------
    INSERT INTO trans.scm_stock (
        merchant_id,
        warehouse_id,
        sku,
        batch_no,
        catalogue_id,
        uom,
        qty_on_hand,
        qty_reserved,
        qty_available,
        cost_price,
        expiry_date,
        ledger_id,
        created_at,
        updated_at
    ) VALUES (
        p_merchant_id,
        p_warehouse_id,
        p_sku,
        p_batch_no,
        p_catalogue_id,
        p_uom,
        GREATEST(v_qty_change, 0),  -- Use calculated change (don't allow negative on insert)
        0,                           -- qty_reserved
        GREATEST(v_qty_change, 0),  -- qty_available
        NULL,                        -- cost_price
        p_expiry_date,              -- expiry_date
        v_ledger_id,
        NOW(),
        NOW()
    )
    ON CONFLICT (merchant_id, warehouse_id, catalogue_id, batch_no)
    DO UPDATE SET
        qty_on_hand   = GREATEST(trans.scm_stock.qty_on_hand + v_qty_change, 0),
        qty_available = GREATEST((trans.scm_stock.qty_on_hand + v_qty_change) - trans.scm_stock.qty_reserved, 0),
        ledger_id     = v_ledger_id,
        updated_at    = NOW();
        
    RAISE NOTICE 'Stock updated. Final qty change applied: %', v_qty_change;
END;
$BODY$;

ALTER FUNCTION trans.apply_stock_movement(text, text, text, text, text, date, text, numeric, text, text, uuid, text, text, text)
    OWNER TO trans_owner;
-- FUNCTION: trans.fn_get_po_item_details(uuid)

-- DROP FUNCTION IF EXISTS trans.fn_get_po_item_details(uuid);

CREATE OR REPLACE FUNCTION trans.fn_get_po_item_details(
	p_po_id uuid)
    RETURNS TABLE(po_id uuid, catalogue_code text, catalogue_name text, hsn_code text, gst_rate numeric, ord_qty numeric, dispatched_qty numeric, rcvd_qty numeric, rejected_qty numeric, returned_qty numeric, net_accepted_qty numeric, unit_price numeric, taxable_amount numeric, gst_amount numeric, final_amount numeric) 
    LANGUAGE 'plpgsql'
    COST 100
    VOLATILE PARALLEL UNSAFE
    ROWS 1000

AS $BODY$
BEGIN
    RETURN QUERY
    SELECT
        poi.po_id,

        cr.catalogue_code,
        cr.catalogue_name,
        cr.hsn_code,
        cr.gst_rate,

        poi.ord_qty,
        poi.dispatched_qty,
        poi.rcvd_qty,
        poi.rejected_qty,
        poi.returned_qty,
        (poi.rcvd_qty - poi.rejected_qty - poi.returned_qty) AS net_accepted_qty,

        poi.unit_price,
        poi.line_amt AS taxable_amount,
        poi.tax_amt AS gst_amount,

        (poi.line_amt + poi.tax_amt) AS final_amount
    FROM trans.scm_po_item poi
    JOIN trans.catalogue_ref cr
        ON cr.catalogue_id = poi.catalogue_id
    WHERE poi.po_id = p_po_id;
END;
$BODY$;

ALTER FUNCTION trans.fn_get_po_item_details(uuid)
    OWNER TO trans_owner;

-- FUNCTION: trans.fn_get_po_item_details(uuid)

-- DROP FUNCTION IF EXISTS trans.fn_get_po_item_details(uuid);

CREATE OR REPLACE FUNCTION trans.fn_get_po_item_details(
	p_po_id uuid)
    RETURNS TABLE(po_id uuid, catalogue_code text, catalogue_name text, hsn_code text, gst_rate numeric, ord_qty numeric, dispatched_qty numeric, rcvd_qty numeric, rejected_qty numeric, returned_qty numeric, net_accepted_qty numeric, unit_price numeric, taxable_amount numeric, gst_amount numeric, final_amount numeric) 
    LANGUAGE 'plpgsql'
    COST 100
    VOLATILE PARALLEL UNSAFE
    ROWS 1000

AS $BODY$
BEGIN
    RETURN QUERY
    SELECT
        poi.po_id,

        cr.catalogue_code,
        cr.catalogue_name,
        cr.hsn_code,
        cr.gst_rate,

        poi.ord_qty,
        poi.dispatched_qty,
        poi.rcvd_qty,
        poi.rejected_qty,
        poi.returned_qty,
        (poi.rcvd_qty - poi.rejected_qty - poi.returned_qty) AS net_accepted_qty,

        poi.unit_price,
        poi.line_amt AS taxable_amount,
        poi.tax_amt AS gst_amount,

        (poi.line_amt + poi.tax_amt) AS final_amount
    FROM trans.scm_po_item poi
    JOIN trans.catalogue_ref cr
        ON cr.catalogue_id = poi.catalogue_id
    WHERE poi.po_id = p_po_id;
END;
$BODY$;

ALTER FUNCTION trans.fn_get_po_item_details(uuid)
    OWNER TO trans_owner;
-- FUNCTION: trans.fn_get_po_items_for_grn(uuid)

-- DROP FUNCTION IF EXISTS trans.fn_get_po_items_for_grn(uuid);

CREATE OR REPLACE FUNCTION trans.fn_get_po_items_for_grn(
	p_po_id uuid)
    RETURNS TABLE(po_item_id uuid, catalogue_id uuid, catalogue_name text, ordered_qty numeric, received_qty numeric, rejected_qty numeric, pending_grn numeric, cost_price numeric) 
    LANGUAGE 'plpgsql'
    COST 100
    VOLATILE PARALLEL UNSAFE
    ROWS 1000

AS $BODY$
BEGIN
    RETURN QUERY
    SELECT
        po.po_item_id AS po_item_id,
        po.catalogue_id,
        cr.catalogue_name,
        po.ord_qty        AS ordered_qty,
        po.rcvd_qty       AS received_qty,
        po.rejected_qty,
        (po.ord_qty - po.rcvd_qty - po.rejected_qty) AS pending_grn,
        po.unit_price     AS cost_price
    FROM trans.scm_po_item po
    JOIN trans.catalogue_ref cr
        ON po.catalogue_id = cr.catalogue_id::uuid
    WHERE po.po_id = p_po_id
      AND (po.ord_qty - po.rcvd_qty - po.rejected_qty) > 0
    ORDER BY cr.catalogue_name;
END;
$BODY$;

ALTER FUNCTION trans.fn_get_po_items_for_grn(uuid)
    OWNER TO trans_owner;
-- FUNCTION: trans.fn_get_po_items_for_order_process(uuid, text, text)

-- DROP FUNCTION IF EXISTS trans.fn_get_po_items_for_order_process(uuid, text, text);

CREATE OR REPLACE FUNCTION trans.fn_get_po_items_for_order_process(
	p_po_id uuid,
	p_warehouse_id text,
	p_mode text)
    RETURNS TABLE(po_item_id uuid, catalogue_id uuid, catalogue_name text, ordered_qty numeric, received_qty numeric, dispatched_qty numeric, pending_qty numeric, qty_available numeric, batch_no character varying, expiry_date date, unit_price numeric) 
    LANGUAGE 'plpgsql'
    COST 100
    VOLATILE PARALLEL UNSAFE
    ROWS 1000

AS $BODY$
BEGIN
    IF p_mode = 'DISPATCH' THEN

        RETURN QUERY
                SELECT
                        po.po_item_id,
                        po.catalogue_id,
                        cr.catalogue_name,
                        po.ord_uom_qty,
                        po.rcvd_qty,
                        0::numeric AS dispatched_qty,
                        (po.ord_uom_qty - 0::numeric) AS pending_qty,
                        st.qty_available,
                        st.batch_no,
                        st.expiry_date,
                        po.unit_price
                FROM trans.scm_po_item po
        JOIN trans.catalogue_ref cr
            ON po.catalogue_id = cr.catalogue_id
        JOIN trans.scm_stock st
            ON st.catalogue_id = po.catalogue_id
           AND st.warehouse_id = p_warehouse_id
        WHERE po.po_id = p_po_id
                      AND (po.ord_uom_qty - 0::numeric) > 0
          AND st.qty_available > 0
        ORDER BY cr.catalogue_name, st.expiry_date;

    ELSIF p_mode = 'INVOICE' THEN

        RETURN QUERY
                SELECT
                        po.po_item_id,
                        po.catalogue_id,
                        cr.catalogue_name,
                        po.ord_uom_qty,
                        po.rcvd_qty,
                        0::numeric AS dispatched_qty,
                        (po.ord_uom_qty - 0::numeric) AS pending_qty,
                        NULL::numeric AS qty_available,
                        NULL::character varying(50)    AS batch_no,
                        NULL::date    AS expiry_date,
                        po.unit_price
                FROM trans.scm_po_item po
        JOIN trans.catalogue_ref cr
            ON po.catalogue_id = cr.catalogue_id
        WHERE po.po_id = p_po_id
                    AND (po.ord_uom_qty - po.returned_qty) > 0
        ORDER BY cr.catalogue_name;

    ELSE
        RAISE EXCEPTION 'Invalid p_mode: %', p_mode;
    END IF;
END;
$BODY$;

ALTER FUNCTION trans.fn_get_po_items_for_order_process(uuid, text, text)
    OWNER TO trans_owner;
-- FUNCTION: trans.fn_get_po_items_for_purchase_return(uuid, character varying)

-- DROP FUNCTION IF EXISTS trans.fn_get_po_items_for_purchase_return(uuid, character varying);
-- DROP FUNCTION IF EXISTS trans.fn_get_po_items_for_purchase_return(uuid, uuid);

CREATE OR REPLACE FUNCTION trans.fn_get_po_items_for_purchase_return(
	p_po_id uuid,
	p_warehouse_id character varying)
    RETURNS TABLE(
        po_item_id uuid, 
        catalogue_id uuid, 
        catalogue_name text, 
        sku text,
        ordered_qty numeric,
        received_qty numeric, 
        returned_qty numeric, 
        dispatched_qty numeric, 
        returnable_qty numeric, 
        batch_no text, 
        expiry_date date, 
        qty_available numeric, 
        unit_price numeric
    ) 
    LANGUAGE 'plpgsql'
    COST 100
    VOLATILE PARALLEL UNSAFE
    ROWS 1000

AS $BODY$
BEGIN
    RETURN QUERY
    SELECT
        pi.po_item_id,
        pi.catalogue_id,
        cr.catalogue_name,
        pi.sku::text,
        pi.ord_qty,
        pi.rcvd_qty,
        pi.returned_qty,
        pi.dispatched_qty,
        (
		COALESCE(pi.rcvd_qty, 0)
		  - COALESCE(pi.rejected_qty, 0)
		  - COALESCE(pi.returned_qty, 0)
		  - COALESCE(pi.dispatched_qty, 0)
		) AS returnable_qty,
        st.batch_no::text,
        st.expiry_date,
        st.qty_available,
        pi.unit_price

    FROM trans.scm_po_item pi
    JOIN trans.catalogue_ref cr
        ON cr.catalogue_id = pi.catalogue_id
    JOIN trans.scm_stock st
        ON st.catalogue_id::uuid = pi.catalogue_id
       AND st.warehouse_id = p_warehouse_id

    WHERE pi.po_id = p_po_id
      AND st.qty_available > 0

    ORDER BY cr.catalogue_name, st.expiry_date;
END;
$BODY$;

ALTER FUNCTION trans.fn_get_po_items_for_purchase_return(uuid, character varying)
    OWNER TO trans_owner;
-- FUNCTION: trans.fn_get_po_ready_for_action(text, character varying)

-- DROP FUNCTION IF EXISTS trans.fn_get_po_ready_for_action(text, character varying);

CREATE OR REPLACE FUNCTION trans.fn_get_po_ready_for_action(
	p_mode text,
	p_client_id character varying DEFAULT NULL::character varying)
    RETURNS TABLE(po_id uuid, po_no character varying, supplier_id character varying, client_id character varying, shipment_count integer, latest_shipment_date date, shipment_no text, lr_no text, transporter text) 
    LANGUAGE 'plpgsql'
    COST 100
    VOLATILE PARALLEL UNSAFE
    ROWS 1000

AS $BODY$
BEGIN
    IF p_mode = 'INVOICE' THEN
        RETURN QUERY
        SELECT
            po.po_id,
            po.po_no,
            po.supplier_id::text,
            po.buyer_id::text,
            COUNT(DISTINCT sh.shipment_id)::integer AS shipment_count,
            MAX(sh.shipment_date)::date AS latest_shipment_date,
            MAX(sh.shipment_no) AS shipment_no,
            MAX(sh.lr_no) AS lr_no,
            MAX(sh.transporter) AS transporter
        FROM trans.scm_po po
        JOIN trans.scm_trade_shipment sh
            ON sh.order_id = po.po_id
           AND sh.status IN ('shipped', 'closed')
        WHERE po.status = 'APPROVED'
          AND (p_client_id IS NULL OR po.buyer_id::text = p_client_id)
        GROUP BY
            po.po_id,
            po.po_no,
            po.supplier_id,
            po.buyer_id
        ORDER BY latest_shipment_date DESC;

    ELSIF p_mode = 'DISPATCH' THEN
        RETURN QUERY
        SELECT
            po.po_id,
            po.po_no,
            po.supplier_id::text,
            po.buyer_id::text,
            COUNT(DISTINCT sh.shipment_id)::integer AS shipment_count,
            MAX(sh.shipment_date)::date AS latest_shipment_date,
            MAX(sh.shipment_no) AS shipment_no,
            MAX(sh.lr_no) AS lr_no,
            MAX(sh.transporter) AS transporter
        FROM trans.scm_po po
        LEFT JOIN trans.scm_trade_shipment sh
            ON sh.order_id = po.po_id
        WHERE po.status = 'APPROVED'
          AND (p_client_id IS NULL OR po.buyer_id::text = p_client_id)
        GROUP BY
            po.po_id,
            po.po_no,
            po.supplier_id,
            po.buyer_id
        ORDER BY latest_shipment_date DESC NULLS FIRST, po.po_no DESC;

    ELSE
        RAISE EXCEPTION 'Invalid p_mode: % (expected DISPATCH or INVOICE)', p_mode;
    END IF;
END;
$BODY$;

ALTER FUNCTION trans.fn_get_po_ready_for_action(text, character varying)
    OWNER TO trans_owner;
-- FUNCTION: trans.fn_get_po_ready_for_purchase_return(character varying)

-- DROP FUNCTION IF EXISTS trans.fn_get_po_ready_for_purchase_return(character varying);

CREATE OR REPLACE FUNCTION trans.fn_get_po_ready_for_purchase_return(
	p_client_id character varying DEFAULT NULL::character varying)
    RETURNS TABLE(po_id uuid, po_no character varying, supplier_id uuid, client_id uuid, returnable_qty numeric, last_grn_date date) 
    LANGUAGE 'plpgsql'
    COST 100
    VOLATILE PARALLEL UNSAFE
    ROWS 1000

AS $BODY$
BEGIN
    RETURN QUERY
    SELECT
        po.po_id,
        po.po_no,
        po.supplier_id,
        po.buyer_id AS client_id,

        SUM(
            pi.rcvd_qty
          - pi.rejected_qty
          - pi.returned_qty
          - pi.dispatched_qty
        ) AS returnable_qty,

        MAX(grn.recv_dt) AS last_grn_date

    FROM trans.scm_po po
    JOIN trans.scm_po_item pi
        ON pi.po_id = po.po_id
    LEFT JOIN trans.scm_grn grn
        ON grn.po_id = po.po_id

    WHERE po.status = 'APPROVED'
      AND (p_client_id IS NULL OR po.buyer_id::text = p_client_id)

    GROUP BY
        po.po_id,
        po.po_no,
        po.supplier_id,
        po.buyer_id

    HAVING
        SUM(
            pi.rcvd_qty
          - pi.rejected_qty
          - pi.returned_qty
          - pi.dispatched_qty
        ) > 0

    ORDER BY last_grn_date DESC;
END;
$BODY$;

ALTER FUNCTION trans.fn_get_po_ready_for_purchase_return(character varying)
    OWNER TO trans_owner;
-- FUNCTION: trans.get_stock_ledger(character varying, character varying, character varying, integer)

-- DROP FUNCTION IF EXISTS trans.get_stock_ledger(character varying, character varying, character varying, integer);

CREATE OR REPLACE FUNCTION trans.get_stock_ledger(
	p_merchant_id character varying,
	p_sku character varying DEFAULT NULL::character varying,
	p_batch_no character varying DEFAULT NULL::character varying,
	p_limit integer DEFAULT 100)
    RETURNS TABLE(ledger_id uuid, warehouse_id character varying, catalogue_id uuid, sku character varying, batch_no character varying, txn_type character varying, qty numeric, uom character varying, ref_type character varying, ref_id uuid, ref_no character varying, remarks text, created_by character varying, created_at timestamp without time zone) 
    LANGUAGE 'plpgsql'
    COST 100
    VOLATILE PARALLEL UNSAFE
    ROWS 1000

AS $BODY$
BEGIN
    RETURN QUERY
    SELECT l.ledger_id, l.warehouse_id, l.catalogue_id, l.sku, l.batch_no,
           l.txn_type, l.qty, l.uom, l.ref_type, l.ref_id, l.ref_no,
           l.remarks, l.created_by, l.created_at
    FROM trans.scm_stock_ledger l
    WHERE l.merchant_id = p_merchant_id
      AND (p_sku IS NULL OR l.sku = p_sku)
      AND (p_batch_no IS NULL OR l.batch_no = p_batch_no)
    ORDER BY l.created_at DESC
    LIMIT p_limit;
END;
$BODY$;

ALTER FUNCTION trans.get_stock_ledger(character varying, character varying, character varying, integer)
    OWNER TO trans_owner;
-- FUNCTION: trans.get_stock_summary(character varying, character varying, character varying)

-- DROP FUNCTION IF EXISTS trans.get_stock_summary(character varying, character varying, character varying);

CREATE OR REPLACE FUNCTION trans.get_stock_summary(
	p_merchant_id character varying,
	p_warehouse_id character varying DEFAULT NULL::character varying,
	p_sku character varying DEFAULT NULL::character varying)
    RETURNS TABLE(stock_id uuid, warehouse_id character varying, catalogue_id uuid, sku character varying, batch_no character varying, qty_on_hand numeric, qty_reserved numeric, qty_available numeric, uom character varying, updated_at timestamp without time zone) 
    LANGUAGE 'plpgsql'
    COST 100
    VOLATILE PARALLEL UNSAFE
    ROWS 1000

AS $BODY$
BEGIN
    RETURN QUERY
    SELECT s.stock_id, s.warehouse_id, s.catalogue_id, s.sku, s.batch_no,
           s.qty_on_hand, s.qty_reserved, s.qty_available, s.uom, s.updated_at
    FROM trans.scm_stock s
    WHERE s.merchant_id = p_merchant_id
      AND (p_warehouse_id IS NULL OR s.warehouse_id = p_warehouse_id)
      AND (p_sku IS NULL OR s.sku = p_sku)
    ORDER BY s.sku, s.batch_no;
END;
$BODY$;

ALTER FUNCTION trans.get_stock_summary(character varying, character varying, character varying)
    OWNER TO trans_owner;
-- FUNCTION: trans.get_trade_sales_analytics(character varying, character varying, date, date)

-- DROP FUNCTION IF EXISTS trans.get_trade_sales_analytics(character varying, character varying, date, date);

CREATE OR REPLACE FUNCTION trans.get_trade_sales_analytics(
	p_supplier_id character varying DEFAULT NULL::character varying,
	p_client_id character varying DEFAULT NULL::character varying,
	p_date_from date DEFAULT NULL::date,
	p_date_to date DEFAULT NULL::date)
    RETURNS TABLE(metric_name character varying, metric_value numeric, metric_unit character varying) 
    LANGUAGE 'plpgsql'
    COST 100
    VOLATILE PARALLEL UNSAFE
    ROWS 1000

AS $BODY$
BEGIN
    -- Total shipments
    RETURN QUERY
    SELECT 
        'total_shipments'::VARCHAR(50) as metric_name,
        COUNT(*)::NUMERIC as metric_value,
        'count'::VARCHAR(20) as metric_unit
    FROM trans.scm_trade_shipment ts
    WHERE (p_supplier_id IS NULL OR ts.supplier_id = p_supplier_id)
      AND (p_client_id IS NULL OR ts.client_id = p_client_id)
      AND (p_date_from IS NULL OR ts.shipment_date >= p_date_from)
      AND (p_date_to IS NULL OR ts.shipment_date <= p_date_to);
    
    -- Total quantity shipped
    RETURN QUERY
    SELECT 
        'total_qty_shipped'::VARCHAR(50) as metric_name,
        COALESCE(SUM(tsi.shipped_qty), 0)::NUMERIC as metric_value,
        'units'::VARCHAR(20) as metric_unit
    FROM trans.scm_trade_shipment ts
    JOIN trans.scm_trade_shipment_item tsi ON ts.shipment_id = tsi.shipment_id
    WHERE (p_supplier_id IS NULL OR ts.supplier_id = p_supplier_id)
      AND (p_client_id IS NULL OR ts.client_id = p_client_id)
      AND (p_date_from IS NULL OR ts.shipment_date >= p_date_from)
      AND (p_date_to IS NULL OR ts.shipment_date <= p_date_to);
    
    -- Unique SKUs shipped
    RETURN QUERY
    SELECT 
        'unique_skus'::VARCHAR(50) as metric_name,
        COUNT(DISTINCT tsi.sku)::NUMERIC as metric_value,
        'count'::VARCHAR(20) as metric_unit
    FROM trans.scm_trade_shipment ts
    JOIN trans.scm_trade_shipment_item tsi ON ts.shipment_id = tsi.shipment_id
    WHERE (p_supplier_id IS NULL OR ts.supplier_id = p_supplier_id)
      AND (p_client_id IS NULL OR ts.client_id = p_client_id)
      AND (p_date_from IS NULL OR ts.shipment_date >= p_date_from)
      AND (p_date_to IS NULL OR ts.shipment_date <= p_date_to);
    
    -- Average shipment size
    RETURN QUERY
    SELECT 
        'avg_shipment_size'::VARCHAR(50) as metric_name,
        COALESCE(AVG(shipment_totals.total_qty), 0)::NUMERIC as metric_value,
        'units'::VARCHAR(20) as metric_unit
    FROM (
        SELECT ts.shipment_id, SUM(tsi.shipped_qty) as total_qty
        FROM trans.scm_trade_shipment ts
        JOIN trans.scm_trade_shipment_item tsi ON ts.shipment_id = tsi.shipment_id
        WHERE (p_supplier_id IS NULL OR ts.supplier_id = p_supplier_id)
          AND (p_client_id IS NULL OR ts.client_id = p_client_id)
          AND (p_date_from IS NULL OR ts.shipment_date >= p_date_from)
          AND (p_date_to IS NULL OR ts.shipment_date <= p_date_to)
        GROUP BY ts.shipment_id
    ) shipment_totals;
    
END;
$BODY$;

ALTER FUNCTION trans.get_trade_sales_analytics(character varying, character varying, date, date)
    OWNER TO trans_owner;
-- FUNCTION: trans.release_stock_reservation(character varying, character varying, character varying, character varying, numeric, uuid, character varying)

-- DROP FUNCTION IF EXISTS trans.release_stock_reservation(character varying, character varying, character varying, character varying, numeric, uuid, character varying);

CREATE OR REPLACE FUNCTION trans.release_stock_reservation(
	p_merchant_id character varying,
	p_warehouse_id character varying,
	p_sku character varying,
	p_batch_no character varying,
	p_qty numeric,
	p_ref_id uuid,
	p_created_by character varying)
    RETURNS boolean
    LANGUAGE 'plpgsql'
    COST 100
    VOLATILE PARALLEL UNSAFE
AS $BODY$
BEGIN
    -- Update stock reservation
    UPDATE trans.scm_stock
    SET qty_reserved = GREATEST(qty_reserved - p_qty, 0),
        qty_available = qty_on_hand - GREATEST(qty_reserved - p_qty, 0),
        updated_at = NOW()
    WHERE merchant_id = p_merchant_id
      AND warehouse_id = p_warehouse_id
      AND sku = p_sku
      AND batch_no = p_batch_no;
    
    RETURN TRUE;
END;
$BODY$;

ALTER FUNCTION trans.release_stock_reservation(character varying, character varying, character varying, character varying, numeric, uuid, character varying)
    OWNER TO trans_owner;
-- FUNCTION: trans.reserve_stock(character varying, character varying, character varying, character varying, numeric, uuid, character varying)

-- DROP FUNCTION IF EXISTS trans.reserve_stock(character varying, character varying, character varying, character varying, numeric, uuid, character varying);

CREATE OR REPLACE FUNCTION trans.reserve_stock(
	p_merchant_id character varying,
	p_warehouse_id character varying,
	p_sku character varying,
	p_batch_no character varying,
	p_qty numeric,
	p_ref_id uuid,
	p_created_by character varying)
    RETURNS boolean
    LANGUAGE 'plpgsql'
    COST 100
    VOLATILE PARALLEL UNSAFE
AS $BODY$
DECLARE
    v_available_qty NUMERIC(14,3);
BEGIN
    -- Get current available quantity
    SELECT qty_available
    INTO v_available_qty
    FROM trans.scm_stock
    WHERE merchant_id = p_merchant_id
      AND warehouse_id = p_warehouse_id
      AND sku = p_sku
      AND batch_no = p_batch_no;
    
    -- Check if sufficient stock available
    IF v_available_qty IS NULL OR v_available_qty < p_qty THEN
        RAISE EXCEPTION 'Insufficient available stock for SKU: %. Available: %, Required: %', 
            p_sku, COALESCE(v_available_qty, 0), p_qty;
    END IF;
    
    -- Update stock reservation
    UPDATE trans.scm_stock
    SET qty_reserved = qty_reserved + p_qty,
        qty_available = qty_available - p_qty,
        updated_at = NOW()
    WHERE merchant_id = p_merchant_id
      AND warehouse_id = p_warehouse_id
      AND sku = p_sku
      AND batch_no = p_batch_no;
    
    RETURN TRUE;
END;
$BODY$;

ALTER FUNCTION trans.reserve_stock(character varying, character varying, character varying, character varying, numeric, uuid, character varying)
    OWNER TO trans_owner;

-- FUNCTION: trans.update_adjustment_note_timestamp()
CREATE OR REPLACE FUNCTION trans.update_adjustment_note_timestamp()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

ALTER FUNCTION trans.update_adjustment_note_timestamp()
    OWNER TO trans_owner;

-- FUNCTION: trans.update_trade_shipment_updated_at()
CREATE OR REPLACE FUNCTION trans.update_trade_shipment_updated_at()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

ALTER FUNCTION trans.update_trade_shipment_updated_at()
    OWNER TO trans_owner;

-- FUNCTION: trans.get_next_scheme_sequence(integer)
CREATE OR REPLACE FUNCTION trans.get_next_scheme_sequence(scheme_year INTEGER)
RETURNS INTEGER AS $$
DECLARE
    next_seq INTEGER;
BEGIN
    SELECT COALESCE(MAX(
        CAST(SUBSTRING(scheme_code FROM 'SCH-[0-9]{4}-([0-9]{6})') AS INTEGER)
    ), 0) + 1
    INTO next_seq
    FROM trans.trade_schemes
    WHERE scheme_code LIKE 'SCH-' || scheme_year || '-%';
    
    RETURN next_seq;
END;
$$ LANGUAGE plpgsql;

ALTER FUNCTION trans.get_next_scheme_sequence(integer)
    OWNER TO trans_owner;
