Spaces:
Running
Running
| -- 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; | |