vanitha commited on
Commit
1e111cb
·
1 Parent(s): 1720144

added all sql stored function

Browse files
app/sql/apply_bulk_stock_movements.sql ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- FUNCTION: trans.apply_bulk_stock_movements(jsonb)
2
+
3
+ -- DROP FUNCTION IF EXISTS trans.apply_bulk_stock_movements(jsonb);
4
+
5
+ CREATE OR REPLACE FUNCTION trans.apply_bulk_stock_movements(
6
+ p_movements jsonb)
7
+ RETURNS jsonb
8
+ LANGUAGE 'plpgsql'
9
+ COST 100
10
+ VOLATILE PARALLEL UNSAFE
11
+ AS $BODY$
12
+ DECLARE
13
+ v_movement JSONB;
14
+ v_results JSONB := '[]'::JSONB;
15
+ v_result JSONB;
16
+ v_success BOOLEAN := TRUE;
17
+ v_error_msg TEXT;
18
+ BEGIN
19
+ -- Process each movement in the array
20
+ FOR v_movement IN SELECT * FROM jsonb_array_elements(p_movements)
21
+ LOOP
22
+ BEGIN
23
+ -- Apply individual stock movement using the 9-parameter function
24
+ PERFORM trans.apply_stock_movement(
25
+ (v_movement->>'merchant_id')::TEXT,
26
+ (v_movement->>'location_id')::TEXT,
27
+ (v_movement->>'sku')::TEXT,
28
+ (v_movement->>'batch_no')::TEXT,
29
+ (v_movement->>'catalogue_id')::TEXT,
30
+ (v_movement->>'expiry_date')::DATE,
31
+ (v_movement->>'uom')::TEXT,
32
+ (v_movement->>'qty')::NUMERIC,
33
+ (v_movement->>'txn_type')::TEXT,
34
+ (v_movement->>'ref_type')::TEXT,
35
+ (v_movement->>'ref_id')::UUID,
36
+ (v_movement->>'created_by')::TEXT
37
+ );
38
+
39
+ -- Build success result object
40
+ v_result := jsonb_build_object(
41
+ 'ledger_id', gen_random_uuid(),
42
+ 'sku', v_movement->>'sku',
43
+ 'qty', v_movement->>'qty',
44
+ 'success', true,
45
+ 'error', null
46
+ );
47
+
48
+ EXCEPTION WHEN OTHERS THEN
49
+ -- Handle individual movement errors
50
+ v_success := FALSE;
51
+ v_error_msg := SQLERRM;
52
+
53
+ v_result := jsonb_build_object(
54
+ 'ledger_id', null,
55
+ 'sku', v_movement->>'sku',
56
+ 'qty', v_movement->>'qty',
57
+ 'success', false,
58
+ 'error', v_error_msg
59
+ );
60
+ END;
61
+
62
+ -- Add to results array
63
+ v_results := v_results || v_result;
64
+ END LOOP;
65
+
66
+ -- If any movement failed, rollback the entire transaction
67
+ IF NOT v_success THEN
68
+ RAISE EXCEPTION 'One or more stock movements failed. Transaction rolled back.';
69
+ END IF;
70
+
71
+ RETURN v_results;
72
+ END;
73
+ $BODY$;
74
+
75
+ ALTER FUNCTION trans.apply_bulk_stock_movements(jsonb)
76
+ OWNER TO trans_owner;
app/sql/apply_stock_movement.sql ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- FUNCTION: trans.apply_stock_movement(text, text, text, text, text, date, text, numeric, text, text, uuid, text)
2
+
3
+ -- DROP FUNCTION IF EXISTS trans.apply_stock_movement(text, text, text, text, text, date, text, numeric, text, text, uuid, text);
4
+
5
+ CREATE OR REPLACE FUNCTION trans.apply_stock_movement(
6
+ p_merchant_id text,
7
+ p_location_id text,
8
+ p_sku text,
9
+ p_batch_no text,
10
+ p_catalogue_id text,
11
+ p_expiry_date date,
12
+ p_uom text,
13
+ p_qty numeric,
14
+ p_txn_type text,
15
+ p_ref_type text,
16
+ p_ref_id uuid,
17
+ p_user text)
18
+ RETURNS void
19
+ LANGUAGE 'plpgsql'
20
+ COST 100
21
+ VOLATILE PARALLEL UNSAFE
22
+ AS $BODY$
23
+ DECLARE
24
+ v_ledger_id uuid := gen_random_uuid();
25
+ v_qty_change numeric;
26
+ BEGIN
27
+ ----------------------------------------------------------------
28
+ -- Idempotency check
29
+ ----------------------------------------------------------------
30
+ IF EXISTS (
31
+ SELECT 1
32
+ FROM trans.scm_stock_ledger
33
+ WHERE ref_type = p_ref_type
34
+ AND ref_id = p_ref_id
35
+ AND sku = p_sku
36
+ AND batch_no = p_batch_no
37
+ ) THEN
38
+ RETURN;
39
+ END IF;
40
+
41
+ ----------------------------------------------------------------
42
+ -- Determine quantity change based on transaction type
43
+ ----------------------------------------------------------------
44
+ CASE p_txn_type
45
+ WHEN 'GRN_IN' THEN
46
+ v_qty_change := p_qty; -- Add stock
47
+ WHEN 'ADJUST_IN' THEN
48
+ v_qty_change := p_qty; -- Add stock
49
+ WHEN 'RETURN_IN' THEN
50
+ v_qty_change := p_qty; -- Add stock
51
+ WHEN 'TRANSFER_IN' THEN
52
+ v_qty_change := p_qty; -- Add stock
53
+ WHEN 'ADJUST_OUT' THEN
54
+ v_qty_change := -p_qty; -- Subtract stock (negate the positive qty)
55
+ WHEN 'SALE_OUT' THEN
56
+ v_qty_change := -p_qty; -- Subtract stock
57
+ WHEN 'TRANSFER_OUT' THEN
58
+ v_qty_change := -p_qty; -- Subtract stock
59
+ ELSE
60
+ RAISE EXCEPTION 'Unknown transaction type: %', p_txn_type;
61
+ END CASE;
62
+
63
+ Raise Notice 'in';
64
+ RAISE NOTICE 'Transaction type: %, Input qty: %, Calculated change: %',
65
+ p_txn_type, p_qty, v_qty_change;
66
+
67
+ RAISE NOTICE 'Inserting ledger: %, %, %, %, %, %, %, %, %, %, %',
68
+ v_ledger_id,
69
+ p_merchant_id,
70
+ p_location_id,
71
+ p_sku,
72
+ p_batch_no,
73
+ p_txn_type,
74
+ v_qty_change, -- Store the calculated change in ledger
75
+ p_ref_type,
76
+ p_ref_id,
77
+ p_user,
78
+ NOW();
79
+
80
+ ----------------------------------------------------------------
81
+ -- Ledger insert (store the ACTUAL qty change with proper sign)
82
+ ----------------------------------------------------------------
83
+ INSERT INTO trans.scm_stock_ledger (
84
+ ledger_id,
85
+ merchant_id,
86
+ location_id,
87
+ sku,
88
+ batch_no,
89
+ txn_type,
90
+ qty,
91
+ ref_type,
92
+ ref_id,
93
+ created_by,
94
+ created_at
95
+ ) VALUES (
96
+ v_ledger_id,
97
+ p_merchant_id,
98
+ p_location_id,
99
+ p_sku,
100
+ p_batch_no,
101
+ p_txn_type,
102
+ v_qty_change, -- Store with correct sign
103
+ p_ref_type,
104
+ p_ref_id,
105
+ p_user,
106
+ NOW()
107
+ );
108
+
109
+ RAISE NOTICE 'Stock ledger done';
110
+
111
+ ----------------------------------------------------------------
112
+ -- Stock snapshot upsert (ALL TABLE COLUMNS)
113
+ ----------------------------------------------------------------
114
+ INSERT INTO trans.scm_stock (
115
+ merchant_id,
116
+ location_id,
117
+ sku,
118
+ batch_no,
119
+ catalogue_id,
120
+ uom,
121
+ qty_on_hand,
122
+ qty_reserved,
123
+ qty_available,
124
+ cost_price,
125
+ expiry_date,
126
+ ledger_id,
127
+ created_at,
128
+ updated_at
129
+ ) VALUES (
130
+ p_merchant_id,
131
+ p_location_id,
132
+ p_sku,
133
+ p_batch_no,
134
+ p_catalogue_id,
135
+ p_uom,
136
+ GREATEST(v_qty_change, 0), -- Use calculated change (don't allow negative on insert)
137
+ 0, -- qty_reserved
138
+ GREATEST(v_qty_change, 0), -- qty_available
139
+ NULL, -- cost_price
140
+ p_expiry_date, -- expiry_date
141
+ v_ledger_id,
142
+ NOW(),
143
+ NOW()
144
+ )
145
+ ON CONFLICT (merchant_id, location_id, catalogue_id, batch_no)
146
+ DO UPDATE SET
147
+ qty_on_hand = GREATEST(trans.scm_stock.qty_on_hand + v_qty_change, 0),
148
+ qty_available = GREATEST((trans.scm_stock.qty_on_hand + v_qty_change) - trans.scm_stock.qty_reserved, 0),
149
+ ledger_id = v_ledger_id,
150
+ updated_at = NOW();
151
+
152
+ RAISE NOTICE 'Stock updated. Final qty change applied: %', v_qty_change;
153
+ END;
154
+ $BODY$;
155
+
156
+ ALTER FUNCTION trans.apply_stock_movement(text, text, text, text, text, date, text, numeric, text, text, uuid, text)
157
+ OWNER TO trans_owner;
app/sql/fn_get_po_items_details.sql ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- FUNCTION: trans.fn_get_po_item_details(uuid)
2
+
3
+ -- DROP FUNCTION IF EXISTS trans.fn_get_po_item_details(uuid);
4
+
5
+ CREATE OR REPLACE FUNCTION trans.fn_get_po_item_details(
6
+ p_po_id uuid)
7
+ 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)
8
+ LANGUAGE 'plpgsql'
9
+ COST 100
10
+ VOLATILE PARALLEL UNSAFE
11
+ ROWS 1000
12
+
13
+ AS $BODY$
14
+ BEGIN
15
+ RETURN QUERY
16
+ SELECT
17
+ poi.po_id,
18
+
19
+ cr.catalogue_code,
20
+ cr.catalogue_name,
21
+ cr.hsn_code,
22
+ cr.gst_rate,
23
+
24
+ poi.ord_qty,
25
+ poi.dispatched_qty,
26
+ poi.rcvd_qty,
27
+ poi.rejected_qty,
28
+ poi.returned_qty,
29
+ (poi.rcvd_qty - poi.rejected_qty - poi.returned_qty) AS net_accepted_qty,
30
+
31
+ poi.unit_price,
32
+ poi.line_amt AS taxable_amount,
33
+ poi.tax_amt AS gst_amount,
34
+
35
+ (poi.line_amt + poi.tax_amt) AS final_amount
36
+ FROM trans.scm_po_item poi
37
+ JOIN trans.catalogue_ref cr
38
+ ON cr.catalogue_id = poi.catalogue_id
39
+ WHERE poi.po_id = p_po_id;
40
+ END;
41
+ $BODY$;
42
+
43
+ ALTER FUNCTION trans.fn_get_po_item_details(uuid)
44
+ OWNER TO trans_owner;
app/sql/fn_get_po_items_for_grn.sql ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- FUNCTION: trans.fn_get_po_items_for_grn(uuid)
2
+
3
+ -- DROP FUNCTION IF EXISTS trans.fn_get_po_items_for_grn(uuid);
4
+
5
+ CREATE OR REPLACE FUNCTION trans.fn_get_po_items_for_grn(
6
+ p_po_id uuid)
7
+ 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)
8
+ LANGUAGE 'plpgsql'
9
+ COST 100
10
+ VOLATILE PARALLEL UNSAFE
11
+ ROWS 1000
12
+
13
+ AS $BODY$
14
+ BEGIN
15
+ RETURN QUERY
16
+ SELECT
17
+ po.id AS po_item_id,
18
+ po.catalogue_id,
19
+ cr.catalogue_name,
20
+ po.ord_qty AS ordered_qty,
21
+ po.rcvd_qty AS received_qty,
22
+ po.rejected_qty,
23
+ (po.ord_qty - po.rcvd_qty - po.rejected_qty) AS pending_grn,
24
+ po.cost_price
25
+ FROM scm_po_item po
26
+ JOIN catalogue_ref cr
27
+ ON po.catalogue_id = cr.catalogue_id
28
+ WHERE po.po_id = p_po_id
29
+ AND (po.ord_qty - po.rcvd_qty - po.rejected_qty) > 0
30
+ ORDER BY cr.catalogue_name;
31
+ END;
32
+ $BODY$;
33
+
34
+ ALTER FUNCTION trans.fn_get_po_items_for_grn(uuid)
35
+ OWNER TO trans_owner;
app/sql/fn_get_po_items_for_order_process.sql ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- FUNCTION: trans.fn_get_po_items_for_order_process(uuid, text, text)
2
+
3
+ -- DROP FUNCTION IF EXISTS trans.fn_get_po_items_for_order_process(uuid, text, text);
4
+
5
+ CREATE OR REPLACE FUNCTION trans.fn_get_po_items_for_order_process(
6
+ p_po_id uuid,
7
+ p_warehouse_id text,
8
+ p_mode text)
9
+ 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)
10
+ LANGUAGE 'plpgsql'
11
+ COST 100
12
+ VOLATILE PARALLEL UNSAFE
13
+ ROWS 1000
14
+
15
+ AS $BODY$
16
+ BEGIN
17
+ IF p_mode = 'DISPATCH' THEN
18
+
19
+ RETURN QUERY
20
+ SELECT
21
+ po.po_id,
22
+ po.catalogue_id,
23
+ cr.catalogue_name,
24
+ po.ord_qty,
25
+ po.rcvd_qty,
26
+ po.dispatched_qty,
27
+ (po.rcvd_qty - po.dispatched_qty) AS pending_qty,
28
+ st.qty_available,
29
+ st.batch_no,
30
+ st.expiry_date,
31
+ po.unit_price
32
+ FROM trans.scm_po_item po
33
+ JOIN trans.catalogue_ref cr
34
+ ON po.catalogue_id = cr.catalogue_id
35
+ JOIN trans.scm_stock st
36
+ ON st.catalogue_id = po.catalogue_id
37
+ AND st.location_id = p_warehouse_id
38
+ WHERE po.po_id = p_po_id
39
+ AND (po.ord_qty - coalesce(po.dispatched_qty,0)) > 0
40
+ AND st.qty_available > 0
41
+ ORDER BY cr.catalogue_name, st.expiry_date;
42
+
43
+ ELSIF p_mode = 'INVOICE' THEN
44
+
45
+ RETURN QUERY
46
+ SELECT
47
+ po.po_id,
48
+ po.catalogue_id,
49
+ cr.catalogue_name,
50
+ po.ord_qty,
51
+ po.rcvd_qty,
52
+ po.dispatched_qty,
53
+ (po.ord_qty - po.dispatched_qty) AS pending_qty,
54
+ NULL::numeric AS qty_available,
55
+ NULL::character varying(50) AS batch_no,
56
+ NULL::date AS expiry_date,
57
+ po.unit_price
58
+ FROM trans.scm_po_item po
59
+ JOIN trans.catalogue_ref cr
60
+ ON po.catalogue_id = cr.catalogue_id
61
+ WHERE po.po_id = p_po_id
62
+ AND (po.dispatched_qty - po.returned_qty) > 0
63
+ ORDER BY cr.catalogue_name;
64
+
65
+ ELSE
66
+ RAISE EXCEPTION 'Invalid p_mode: %', p_mode;
67
+ END IF;
68
+ END;
69
+ $BODY$;
70
+
71
+ ALTER FUNCTION trans.fn_get_po_items_for_order_process(uuid, text, text)
72
+ OWNER TO trans_owner;
app/sql/fn_get_po_items_for_purchase_return.sql ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- FUNCTION: trans.fn_get_po_items_for_purchase_return(uuid, uuid)
2
+
3
+ -- DROP FUNCTION IF EXISTS trans.fn_get_po_items_for_purchase_return(uuid, uuid);
4
+
5
+ CREATE OR REPLACE FUNCTION trans.fn_get_po_items_for_purchase_return(
6
+ p_po_id uuid,
7
+ p_warehouse_id uuid)
8
+ RETURNS TABLE(po_item_id uuid, catalogue_id uuid, catalogue_name text, received_qty numeric, returned_qty numeric, dispatched_qty numeric, returnable_qty numeric, batch_no text, expiry_date date, qty_available numeric, cost_price numeric)
9
+ LANGUAGE 'plpgsql'
10
+ COST 100
11
+ VOLATILE PARALLEL UNSAFE
12
+ ROWS 1000
13
+
14
+ AS $BODY$
15
+ BEGIN
16
+ RETURN QUERY
17
+ SELECT
18
+ pi.po_item_id,
19
+ pi.catalogue_id,
20
+ cr.catalogue_name,
21
+ pi.rcvd_qty,
22
+ pi.returned_qty,
23
+ pi.dispatched_qty,
24
+ (
25
+ COALESCE(pi.rcvd_qty, 0)
26
+ - COALESCE(pi.rejected_qty, 0)
27
+ - COALESCE(pi.returned_qty, 0)
28
+ - COALESCE(pi.dispatched_qty, 0)
29
+ ) AS returnable_qty,
30
+ st.batch_no::text,
31
+ st.expiry_date,
32
+ st.qty_available,
33
+ pi.unit_price
34
+
35
+ FROM trans.scm_po_item pi
36
+ JOIN trans.catalogue_ref cr
37
+ ON cr.catalogue_id = pi.catalogue_id
38
+ JOIN trans.scm_stock st
39
+ ON st.catalogue_id = pi.catalogue_id
40
+ AND st.location_id = p_warehouse_id
41
+
42
+ WHERE pi.po_id = p_po_id
43
+ AND st.qty_available > 0
44
+
45
+ ORDER BY cr.catalogue_name, st.expiry_date;
46
+ END;
47
+ $BODY$;
48
+
49
+ ALTER FUNCTION trans.fn_get_po_items_for_purchase_return(uuid, uuid)
50
+ OWNER TO trans_owner;
app/sql/fn_get_po_ready_for_action.sql ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- FUNCTION: trans.fn_get_po_ready_for_action(text, character varying)
2
+
3
+ -- DROP FUNCTION IF EXISTS trans.fn_get_po_ready_for_action(text, character varying);
4
+
5
+ CREATE OR REPLACE FUNCTION trans.fn_get_po_ready_for_action(
6
+ p_mode text,
7
+ p_client_id character varying DEFAULT NULL::character varying)
8
+ 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)
9
+ LANGUAGE 'plpgsql'
10
+ COST 100
11
+ VOLATILE PARALLEL UNSAFE
12
+ ROWS 1000
13
+
14
+ AS $BODY$
15
+ BEGIN
16
+ IF p_mode = 'INVOICE' THEN
17
+ RETURN QUERY
18
+ SELECT
19
+ po.po_id,
20
+ po.po_no,
21
+ po.supplier_id,
22
+ po.buyer_id,
23
+ COUNT(DISTINCT sh.shipment_id)::integer AS shipment_count,
24
+ MAX(sh.shipment_date)::date AS latest_shipment_date,
25
+ MAX(sh.shipment_no) AS shipment_no,
26
+ MAX(sh.lr_no) AS lr_no,
27
+ MAX(sh.transporter) AS transporter
28
+ FROM trans.scm_po po
29
+ JOIN trans.scm_trade_shipment sh
30
+ ON sh.order_id = po.po_id
31
+ AND sh.status = 'COMPLETED'
32
+ WHERE po.status = 'APPROVED'
33
+ AND (p_client_id IS NULL OR po.buyer_id = p_client_id)
34
+ GROUP BY
35
+ po.po_id,
36
+ po.po_no,
37
+ po.supplier_id,
38
+ po.buyer_id
39
+ ORDER BY latest_shipment_date DESC;
40
+
41
+ ELSIF p_mode = 'DISPATCH' THEN
42
+ RETURN QUERY
43
+ SELECT
44
+ po.po_id,
45
+ po.po_no,
46
+ po.supplier_id,
47
+ po.buyer_id,
48
+ COUNT(DISTINCT sh.shipment_id)::integer AS shipment_count,
49
+ MAX(sh.shipment_date)::date AS latest_shipment_date,
50
+ MAX(sh.shipment_no) AS shipment_no,
51
+ MAX(sh.lr_no) AS lr_no,
52
+ MAX(sh.transporter) AS transporter
53
+ FROM trans.scm_po po
54
+ LEFT JOIN trans.scm_trade_shipment sh
55
+ ON sh.order_id = po.po_id
56
+ WHERE po.status = 'APPROVED'
57
+ AND (p_client_id IS NULL OR po.buyer_id = p_client_id)
58
+ GROUP BY
59
+ po.po_id,
60
+ po.po_no,
61
+ po.supplier_id,
62
+ po.buyer_id
63
+ ORDER BY po.po_no DESC;
64
+
65
+ ELSE
66
+ RAISE EXCEPTION 'Invalid p_mode: % (expected DISPATCH or INVOICE)', p_mode;
67
+ END IF;
68
+ END;
69
+ $BODY$;
70
+
71
+ ALTER FUNCTION trans.fn_get_po_ready_for_action(text, character varying)
72
+ OWNER TO trans_owner;
app/sql/fn_get_ready_for_purchase_return.sql ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- FUNCTION: trans.fn_get_po_ready_for_purchase_return(character varying)
2
+
3
+ -- DROP FUNCTION IF EXISTS trans.fn_get_po_ready_for_purchase_return(character varying);
4
+
5
+ CREATE OR REPLACE FUNCTION trans.fn_get_po_ready_for_purchase_return(
6
+ p_client_id character varying DEFAULT NULL::character varying)
7
+ RETURNS TABLE(po_id uuid, po_no character varying, supplier_id character varying, client_id character varying, returnable_qty numeric, last_grn_date date)
8
+ LANGUAGE 'plpgsql'
9
+ COST 100
10
+ VOLATILE PARALLEL UNSAFE
11
+ ROWS 1000
12
+
13
+ AS $BODY$
14
+ BEGIN
15
+ RETURN QUERY
16
+ SELECT
17
+ po.po_id,
18
+ po.po_no,
19
+ po.supplier_id,
20
+ po.client_id,
21
+
22
+ SUM(
23
+ pi.received_qty
24
+ - pi.rejected_qty
25
+ - pi.returned_qty
26
+ - pi.dispatched_qty
27
+ ) AS returnable_qty,
28
+
29
+ MAX(grn.grn_date) AS last_grn_date
30
+
31
+ FROM trans.scm_po po
32
+ JOIN trans.scm_po_item pi
33
+ ON pi.po_id = po.po_id
34
+ LEFT JOIN trans.scm_grn grn
35
+ ON grn.po_id = po.po_id
36
+
37
+ WHERE po.status = 'APPROVED'
38
+ AND (p_client_id IS NULL OR po.client_id = p_client_id)
39
+
40
+ GROUP BY
41
+ po.po_id,
42
+ po.po_no,
43
+ po.supplier_id,
44
+ po.client_id
45
+
46
+ HAVING
47
+ SUM(
48
+ pi.received_qty
49
+ - pi.rejected_qty
50
+ - pi.returned_qty
51
+ - pi.dispatched_qty
52
+ ) > 0
53
+
54
+ ORDER BY last_grn_date DESC;
55
+ END;
56
+ $BODY$;
57
+
58
+ ALTER FUNCTION trans.fn_get_po_ready_for_purchase_return(character varying)
59
+ OWNER TO trans_owner;
app/sql/get_stock_ledger.sql ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- FUNCTION: trans.get_stock_ledger(character varying, character varying, character varying, integer)
2
+
3
+ -- DROP FUNCTION IF EXISTS trans.get_stock_ledger(character varying, character varying, character varying, integer);
4
+
5
+ CREATE OR REPLACE FUNCTION trans.get_stock_ledger(
6
+ p_merchant_id character varying,
7
+ p_sku character varying DEFAULT NULL::character varying,
8
+ p_batch_no character varying DEFAULT NULL::character varying,
9
+ p_limit integer DEFAULT 100)
10
+ RETURNS TABLE(ledger_id uuid, location_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)
11
+ LANGUAGE 'plpgsql'
12
+ COST 100
13
+ VOLATILE PARALLEL UNSAFE
14
+ ROWS 1000
15
+
16
+ AS $BODY$
17
+ BEGIN
18
+ RETURN QUERY
19
+ SELECT l.ledger_id, l.location_id, l.catalogue_id, l.sku, l.batch_no,
20
+ l.txn_type, l.qty, l.uom, l.ref_type, l.ref_id, l.ref_no,
21
+ l.remarks, l.created_by, l.created_at
22
+ FROM trans.scm_stock_ledger l
23
+ WHERE l.merchant_id = p_merchant_id
24
+ AND (p_sku IS NULL OR l.sku = p_sku)
25
+ AND (p_batch_no IS NULL OR l.batch_no = p_batch_no)
26
+ ORDER BY l.created_at DESC
27
+ LIMIT p_limit;
28
+ END;
29
+ $BODY$;
30
+
31
+ ALTER FUNCTION trans.get_stock_ledger(character varying, character varying, character varying, integer)
32
+ OWNER TO trans_owner;
app/sql/get_stock_summary.sql ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- FUNCTION: trans.get_stock_summary(character varying, character varying, character varying)
2
+
3
+ -- DROP FUNCTION IF EXISTS trans.get_stock_summary(character varying, character varying, character varying);
4
+
5
+ CREATE OR REPLACE FUNCTION trans.get_stock_summary(
6
+ p_merchant_id character varying,
7
+ p_location_id character varying DEFAULT NULL::character varying,
8
+ p_sku character varying DEFAULT NULL::character varying)
9
+ RETURNS TABLE(stock_id uuid, location_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, last_updated_at timestamp without time zone)
10
+ LANGUAGE 'plpgsql'
11
+ COST 100
12
+ VOLATILE PARALLEL UNSAFE
13
+ ROWS 1000
14
+
15
+ AS $BODY$
16
+ BEGIN
17
+ RETURN QUERY
18
+ SELECT s.stock_id, s.location_id, s.catalogue_id, s.sku, s.batch_no,
19
+ s.qty_on_hand, s.qty_reserved, s.qty_available, s.uom, s.last_updated_at
20
+ FROM trans.scm_stock s
21
+ WHERE s.merchant_id = p_merchant_id
22
+ AND (p_location_id IS NULL OR s.location_id = p_location_id)
23
+ AND (p_sku IS NULL OR s.sku = p_sku)
24
+ ORDER BY s.sku, s.batch_no;
25
+ END;
26
+ $BODY$;
27
+
28
+ ALTER FUNCTION trans.get_stock_summary(character varying, character varying, character varying)
29
+ OWNER TO trans_owner;
app/sql/get_trade_sales_analytics.sql ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- FUNCTION: trans.get_trade_sales_analytics(character varying, character varying, date, date)
2
+
3
+ -- DROP FUNCTION IF EXISTS trans.get_trade_sales_analytics(character varying, character varying, date, date);
4
+
5
+ CREATE OR REPLACE FUNCTION trans.get_trade_sales_analytics(
6
+ p_supplier_id character varying DEFAULT NULL::character varying,
7
+ p_client_id character varying DEFAULT NULL::character varying,
8
+ p_date_from date DEFAULT NULL::date,
9
+ p_date_to date DEFAULT NULL::date)
10
+ RETURNS TABLE(metric_name character varying, metric_value numeric, metric_unit character varying)
11
+ LANGUAGE 'plpgsql'
12
+ COST 100
13
+ VOLATILE PARALLEL UNSAFE
14
+ ROWS 1000
15
+
16
+ AS $BODY$
17
+ BEGIN
18
+ -- Total shipments
19
+ RETURN QUERY
20
+ SELECT
21
+ 'total_shipments'::VARCHAR(50) as metric_name,
22
+ COUNT(*)::NUMERIC as metric_value,
23
+ 'count'::VARCHAR(20) as metric_unit
24
+ FROM trans.scm_trade_shipment ts
25
+ WHERE (p_supplier_id IS NULL OR ts.supplier_id = p_supplier_id)
26
+ AND (p_client_id IS NULL OR ts.client_id = p_client_id)
27
+ AND (p_date_from IS NULL OR ts.shipment_date >= p_date_from)
28
+ AND (p_date_to IS NULL OR ts.shipment_date <= p_date_to);
29
+
30
+ -- Total quantity shipped
31
+ RETURN QUERY
32
+ SELECT
33
+ 'total_qty_shipped'::VARCHAR(50) as metric_name,
34
+ COALESCE(SUM(tsi.shipped_qty), 0)::NUMERIC as metric_value,
35
+ 'units'::VARCHAR(20) as metric_unit
36
+ FROM trans.scm_trade_shipment ts
37
+ JOIN trans.scm_trade_shipment_item tsi ON ts.shipment_id = tsi.shipment_id
38
+ WHERE (p_supplier_id IS NULL OR ts.supplier_id = p_supplier_id)
39
+ AND (p_client_id IS NULL OR ts.client_id = p_client_id)
40
+ AND (p_date_from IS NULL OR ts.shipment_date >= p_date_from)
41
+ AND (p_date_to IS NULL OR ts.shipment_date <= p_date_to);
42
+
43
+ -- Unique SKUs shipped
44
+ RETURN QUERY
45
+ SELECT
46
+ 'unique_skus'::VARCHAR(50) as metric_name,
47
+ COUNT(DISTINCT tsi.sku)::NUMERIC as metric_value,
48
+ 'count'::VARCHAR(20) as metric_unit
49
+ FROM trans.scm_trade_shipment ts
50
+ JOIN trans.scm_trade_shipment_item tsi ON ts.shipment_id = tsi.shipment_id
51
+ WHERE (p_supplier_id IS NULL OR ts.supplier_id = p_supplier_id)
52
+ AND (p_client_id IS NULL OR ts.client_id = p_client_id)
53
+ AND (p_date_from IS NULL OR ts.shipment_date >= p_date_from)
54
+ AND (p_date_to IS NULL OR ts.shipment_date <= p_date_to);
55
+
56
+ -- Average shipment size
57
+ RETURN QUERY
58
+ SELECT
59
+ 'avg_shipment_size'::VARCHAR(50) as metric_name,
60
+ COALESCE(AVG(shipment_totals.total_qty), 0)::NUMERIC as metric_value,
61
+ 'units'::VARCHAR(20) as metric_unit
62
+ FROM (
63
+ SELECT ts.shipment_id, SUM(tsi.shipped_qty) as total_qty
64
+ FROM trans.scm_trade_shipment ts
65
+ JOIN trans.scm_trade_shipment_item tsi ON ts.shipment_id = tsi.shipment_id
66
+ WHERE (p_supplier_id IS NULL OR ts.supplier_id = p_supplier_id)
67
+ AND (p_client_id IS NULL OR ts.client_id = p_client_id)
68
+ AND (p_date_from IS NULL OR ts.shipment_date >= p_date_from)
69
+ AND (p_date_to IS NULL OR ts.shipment_date <= p_date_to)
70
+ GROUP BY ts.shipment_id
71
+ ) shipment_totals;
72
+
73
+ END;
74
+ $BODY$;
75
+
76
+ ALTER FUNCTION trans.get_trade_sales_analytics(character varying, character varying, date, date)
77
+ OWNER TO trans_owner;
app/sql/release_stock_reservation.sql ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- FUNCTION: trans.release_stock_reservation(character varying, character varying, character varying, character varying, numeric, uuid, character varying)
2
+
3
+ -- DROP FUNCTION IF EXISTS trans.release_stock_reservation(character varying, character varying, character varying, character varying, numeric, uuid, character varying);
4
+
5
+ CREATE OR REPLACE FUNCTION trans.release_stock_reservation(
6
+ p_merchant_id character varying,
7
+ p_location_id character varying,
8
+ p_sku character varying,
9
+ p_batch_no character varying,
10
+ p_qty numeric,
11
+ p_ref_id uuid,
12
+ p_created_by character varying)
13
+ RETURNS boolean
14
+ LANGUAGE 'plpgsql'
15
+ COST 100
16
+ VOLATILE PARALLEL UNSAFE
17
+ AS $BODY$
18
+ BEGIN
19
+ -- Update stock reservation
20
+ UPDATE trans.scm_stock
21
+ SET qty_reserved = GREATEST(qty_reserved - p_qty, 0),
22
+ qty_available = qty_on_hand - GREATEST(qty_reserved - p_qty, 0),
23
+ last_updated_at = NOW()
24
+ WHERE merchant_id = p_merchant_id
25
+ AND location_id = p_location_id
26
+ AND sku = p_sku
27
+ AND batch_no = p_batch_no;
28
+
29
+ RETURN TRUE;
30
+ END;
31
+ $BODY$;
32
+
33
+ ALTER FUNCTION trans.release_stock_reservation(character varying, character varying, character varying, character varying, numeric, uuid, character varying)
34
+ OWNER TO trans_owner;
app/sql/reserve_stock.sql ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- FUNCTION: trans.reserve_stock(character varying, character varying, character varying, character varying, numeric, uuid, character varying)
2
+
3
+ -- DROP FUNCTION IF EXISTS trans.reserve_stock(character varying, character varying, character varying, character varying, numeric, uuid, character varying);
4
+
5
+ CREATE OR REPLACE FUNCTION trans.reserve_stock(
6
+ p_merchant_id character varying,
7
+ p_location_id character varying,
8
+ p_sku character varying,
9
+ p_batch_no character varying,
10
+ p_qty numeric,
11
+ p_ref_id uuid,
12
+ p_created_by character varying)
13
+ RETURNS boolean
14
+ LANGUAGE 'plpgsql'
15
+ COST 100
16
+ VOLATILE PARALLEL UNSAFE
17
+ AS $BODY$
18
+ DECLARE
19
+ v_available_qty NUMERIC(14,3);
20
+ BEGIN
21
+ -- Get current available quantity
22
+ SELECT qty_available
23
+ INTO v_available_qty
24
+ FROM trans.scm_stock
25
+ WHERE merchant_id = p_merchant_id
26
+ AND location_id = p_location_id
27
+ AND sku = p_sku
28
+ AND batch_no = p_batch_no;
29
+
30
+ -- Check if sufficient stock available
31
+ IF v_available_qty IS NULL OR v_available_qty < p_qty THEN
32
+ RAISE EXCEPTION 'Insufficient available stock for SKU: %. Available: %, Required: %',
33
+ p_sku, COALESCE(v_available_qty, 0), p_qty;
34
+ END IF;
35
+
36
+ -- Update stock reservation
37
+ UPDATE trans.scm_stock
38
+ SET qty_reserved = qty_reserved + p_qty,
39
+ qty_available = qty_available - p_qty,
40
+ last_updated_at = NOW()
41
+ WHERE merchant_id = p_merchant_id
42
+ AND location_id = p_location_id
43
+ AND sku = p_sku
44
+ AND batch_no = p_batch_no;
45
+
46
+ RETURN TRUE;
47
+ END;
48
+ $BODY$;
49
+
50
+ ALTER FUNCTION trans.reserve_stock(character varying, character varying, character varying, character varying, numeric, uuid, character varying)
51
+ OWNER TO trans_owner;