MukeshKapoor25 commited on
Commit
47cf637
·
1 Parent(s): 611c7c7

fix(stock,trade_sales): correct stored procedure calls and enhance shipment queries

Browse files

- Change CALL to SELECT for apply_stock_movement function since it returns VOID
- Add catalogue_id and uom fields to shipment order query for complete item details
- Update GROUP BY clause to include catalogue_id and uom for proper aggregation
- Remove misleading comment about missing catalogue_id in stock transaction
- Add comprehensive stock management stored procedures documentation with idempotency checks and bulk movement support
- Improve error handling in bulk stock movement operations with transaction rollback on failure

app/inventory/stock/services/service.py CHANGED
@@ -90,9 +90,9 @@ class StockService:
90
  # Validate transaction data
91
  self._validate_transaction(transaction)
92
 
93
- # Call simplified stored procedure
94
  query = text("""
95
- CALL apply_stock_movement(
96
  :merchant_id, :location_id, :sku, :batch_no, :qty,
97
  :txn_type, :ref_type, :ref_id, :ref_no, :created_by
98
  )
 
90
  # Validate transaction data
91
  self._validate_transaction(transaction)
92
 
93
+ # Call simplified stored procedure - use SELECT since it returns VOID
94
  query = text("""
95
+ SELECT apply_stock_movement(
96
  :merchant_id, :location_id, :sku, :batch_no, :qty,
97
  :txn_type, :ref_type, :ref_id, :ref_no, :created_by
98
  )
app/trade_sales/services/service.py CHANGED
@@ -411,13 +411,13 @@ class TradeSalesService:
411
  async with self.db.begin():
412
  # Validate order exists and get details
413
  order_query = text("""
414
- SELECT po.*, poi.po_item_id, poi.sku, poi.ord_qty,
415
  COALESCE(SUM(tsi.shipped_qty), 0) as already_shipped
416
  FROM trans.scm_po po
417
  JOIN trans.scm_po_item poi ON po.po_id = poi.po_id
418
  LEFT JOIN trans.scm_trade_shipment_item tsi ON poi.po_item_id = tsi.po_item_id
419
  WHERE po.po_id = :order_id
420
- GROUP BY po.po_id, poi.po_item_id, poi.sku, poi.ord_qty
421
  """)
422
 
423
  order_result = await self.db.execute(order_query, {"order_id": str(order_id)})
@@ -491,7 +491,7 @@ class TradeSalesService:
491
  stock_transaction = StockTransaction(
492
  merchant_id=supplier_id,
493
  location_id=str(request.warehouse_id),
494
- catalogue_id=str(order_item.catalogue_id), # Need to get this
495
  sku=ship_item.sku,
496
  batch_no=ship_item.batch_no,
497
  exp_dt=None, # Will be handled by stock service
 
411
  async with self.db.begin():
412
  # Validate order exists and get details
413
  order_query = text("""
414
+ SELECT po.*, poi.po_item_id, poi.sku, poi.ord_qty, poi.catalogue_id, poi.uom,
415
  COALESCE(SUM(tsi.shipped_qty), 0) as already_shipped
416
  FROM trans.scm_po po
417
  JOIN trans.scm_po_item poi ON po.po_id = poi.po_id
418
  LEFT JOIN trans.scm_trade_shipment_item tsi ON poi.po_item_id = tsi.po_item_id
419
  WHERE po.po_id = :order_id
420
+ GROUP BY po.po_id, poi.po_item_id, poi.sku, poi.ord_qty, poi.catalogue_id, poi.uom
421
  """)
422
 
423
  order_result = await self.db.execute(order_query, {"order_id": str(order_id)})
 
491
  stock_transaction = StockTransaction(
492
  merchant_id=supplier_id,
493
  location_id=str(request.warehouse_id),
494
+ catalogue_id=str(order_item.catalogue_id),
495
  sku=ship_item.sku,
496
  batch_no=ship_item.batch_no,
497
  exp_dt=None, # Will be handled by stock service
docs/database/sql/stored_procedures/stock_management_updated.sql ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- Stock Management Stored Procedures - UPDATED VERSION
2
+ -- Centralized inventory mutation logic in PostgreSQL with proper schema references
3
+
4
+ -- Simplified stock movement function with idempotency and proper schema references
5
+ CREATE OR REPLACE FUNCTION apply_stock_movement(
6
+ p_merchant_id TEXT,
7
+ p_location_id TEXT,
8
+ p_sku TEXT,
9
+ p_batch_no TEXT,
10
+ p_qty NUMERIC,
11
+ p_txn_type TEXT,
12
+ p_ref_type TEXT,
13
+ p_ref_id UUID,
14
+ p_ref_no TEXT,
15
+ p_user TEXT
16
+ ) RETURNS VOID AS $$
17
+ BEGIN
18
+ -- Idempotency check - prevent duplicate processing
19
+ IF EXISTS (
20
+ SELECT 1 FROM trans.scm_stock_ledger
21
+ WHERE ref_type = p_ref_type AND ref_id = p_ref_id AND sku = p_sku AND batch_no = p_batch_no
22
+ ) THEN
23
+ RETURN;
24
+ END IF;
25
+
26
+ -- Insert ledger entry (immutable audit trail)
27
+ INSERT INTO trans.scm_stock_ledger (
28
+ ledger_id, merchant_id, location_id, sku, batch_no,
29
+ qty, txn_type, ref_type, ref_id, ref_no, created_by, created_at
30
+ ) VALUES (
31
+ gen_random_uuid(), p_merchant_id, p_location_id, p_sku, p_batch_no,
32
+ p_qty, p_txn_type, p_ref_type, p_ref_id, p_ref_no, p_user, NOW()
33
+ );
34
+
35
+ -- Stock snapshot upsert (create or update)
36
+ INSERT INTO trans.scm_stock (
37
+ stock_id, merchant_id, location_id, sku, batch_no,
38
+ qty_on_hand, qty_reserved, qty_available, created_at, updated_at, last_updated_at
39
+ ) VALUES (
40
+ gen_random_uuid(), p_merchant_id, p_location_id, p_sku, p_batch_no,
41
+ GREATEST(p_qty, 0), 0, GREATEST(p_qty, 0), NOW(), NOW(), NOW()
42
+ )
43
+ ON CONFLICT (merchant_id, location_id, sku, batch_no)
44
+ DO UPDATE SET
45
+ qty_on_hand = trans.scm_stock.qty_on_hand + p_qty,
46
+ qty_available = GREATEST(trans.scm_stock.qty_on_hand + p_qty - trans.scm_stock.qty_reserved, 0),
47
+ updated_at = NOW(),
48
+ last_updated_at = NOW();
49
+ END;
50
+ $$ LANGUAGE plpgsql;
51
+
52
+ -- Enhanced bulk stock movement function with proper error handling
53
+ CREATE OR REPLACE FUNCTION apply_bulk_stock_movements(
54
+ p_movements JSONB
55
+ ) RETURNS JSONB
56
+ LANGUAGE plpgsql
57
+ AS $$
58
+ DECLARE
59
+ v_movement JSONB;
60
+ v_results JSONB := '[]'::JSONB;
61
+ v_result JSONB;
62
+ v_success BOOLEAN := TRUE;
63
+ v_error_msg TEXT;
64
+ BEGIN
65
+ -- Process each movement in the array
66
+ FOR v_movement IN SELECT * FROM jsonb_array_elements(p_movements)
67
+ LOOP
68
+ BEGIN
69
+ -- Apply individual stock movement
70
+ PERFORM apply_stock_movement(
71
+ (v_movement->>'merchant_id')::TEXT,
72
+ (v_movement->>'location_id')::TEXT,
73
+ (v_movement->>'sku')::TEXT,
74
+ (v_movement->>'batch_no')::TEXT,
75
+ (v_movement->>'qty')::NUMERIC,
76
+ (v_movement->>'txn_type')::TEXT,
77
+ (v_movement->>'ref_type')::TEXT,
78
+ (v_movement->>'ref_id')::UUID,
79
+ (v_movement->>'ref_no')::TEXT,
80
+ (v_movement->>'created_by')::TEXT
81
+ );
82
+
83
+ -- Build success result object
84
+ v_result := jsonb_build_object(
85
+ 'ledger_id', gen_random_uuid(),
86
+ 'sku', v_movement->>'sku',
87
+ 'qty', v_movement->>'qty',
88
+ 'success', true,
89
+ 'error', null
90
+ );
91
+
92
+ EXCEPTION WHEN OTHERS THEN
93
+ -- Handle individual movement errors
94
+ v_success := FALSE;
95
+ v_error_msg := SQLERRM;
96
+
97
+ v_result := jsonb_build_object(
98
+ 'ledger_id', null,
99
+ 'sku', v_movement->>'sku',
100
+ 'qty', v_movement->>'qty',
101
+ 'success', false,
102
+ 'error', v_error_msg
103
+ );
104
+ END;
105
+
106
+ -- Add to results array
107
+ v_results := v_results || v_result;
108
+ END LOOP;
109
+
110
+ -- If any movement failed, rollback the entire transaction
111
+ IF NOT v_success THEN
112
+ RAISE EXCEPTION 'One or more stock movements failed. Transaction rolled back.';
113
+ END IF;
114
+
115
+ RETURN v_results;
116
+ END;
117
+ $$;
118
+
119
+ -- Function to reserve stock (for sales orders) with proper schema
120
+ CREATE OR REPLACE FUNCTION reserve_stock(
121
+ p_merchant_id VARCHAR(64),
122
+ p_location_id VARCHAR(64),
123
+ p_sku VARCHAR(64),
124
+ p_batch_no VARCHAR(50),
125
+ p_qty NUMERIC(14,3),
126
+ p_ref_id UUID,
127
+ p_created_by VARCHAR(64)
128
+ ) RETURNS BOOLEAN
129
+ LANGUAGE plpgsql
130
+ AS $$
131
+ DECLARE
132
+ v_available_qty NUMERIC(14,3);
133
+ BEGIN
134
+ -- Get current available quantity
135
+ SELECT qty_available
136
+ INTO v_available_qty
137
+ FROM trans.scm_stock
138
+ WHERE merchant_id = p_merchant_id
139
+ AND location_id = p_location_id
140
+ AND sku = p_sku
141
+ AND batch_no = p_batch_no;
142
+
143
+ -- Check if sufficient stock available
144
+ IF v_available_qty IS NULL OR v_available_qty < p_qty THEN
145
+ RAISE EXCEPTION 'Insufficient available stock for SKU: %. Available: %, Required: %',
146
+ p_sku, COALESCE(v_available_qty, 0), p_qty;
147
+ END IF;
148
+
149
+ -- Update stock reservation
150
+ UPDATE trans.scm_stock
151
+ SET qty_reserved = qty_reserved + p_qty,
152
+ qty_available = qty_available - p_qty,
153
+ updated_at = NOW(),
154
+ last_updated_at = NOW()
155
+ WHERE merchant_id = p_merchant_id
156
+ AND location_id = p_location_id
157
+ AND sku = p_sku
158
+ AND batch_no = p_batch_no;
159
+
160
+ RETURN TRUE;
161
+ END;
162
+ $$;
163
+
164
+ -- Function to release stock reservation with proper schema
165
+ CREATE OR REPLACE FUNCTION release_stock_reservation(
166
+ p_merchant_id VARCHAR(64),
167
+ p_location_id VARCHAR(64),
168
+ p_sku VARCHAR(64),
169
+ p_batch_no VARCHAR(50),
170
+ p_qty NUMERIC(14,3),
171
+ p_ref_id UUID,
172
+ p_created_by VARCHAR(64)
173
+ ) RETURNS BOOLEAN
174
+ LANGUAGE plpgsql
175
+ AS $$
176
+ BEGIN
177
+ -- Update stock reservation
178
+ UPDATE trans.scm_stock
179
+ SET qty_reserved = GREATEST(qty_reserved - p_qty, 0),
180
+ qty_available = qty_on_hand - GREATEST(qty_reserved - p_qty, 0),
181
+ updated_at = NOW(),
182
+ last_updated_at = NOW()
183
+ WHERE merchant_id = p_merchant_id
184
+ AND location_id = p_location_id
185
+ AND sku = p_sku
186
+ AND batch_no = p_batch_no;
187
+
188
+ RETURN TRUE;
189
+ END;
190
+ $$;
191
+
192
+ -- Function to get current stock summary with proper schema
193
+ CREATE OR REPLACE FUNCTION get_stock_summary(
194
+ p_merchant_id VARCHAR(64),
195
+ p_location_id VARCHAR(64) DEFAULT NULL,
196
+ p_sku VARCHAR(64) DEFAULT NULL
197
+ ) RETURNS TABLE (
198
+ stock_id UUID,
199
+ location_id VARCHAR(64),
200
+ catalogue_id UUID,
201
+ sku VARCHAR(64),
202
+ batch_no VARCHAR(50),
203
+ qty_on_hand NUMERIC(14,3),
204
+ qty_reserved NUMERIC(14,3),
205
+ qty_available NUMERIC(14,3),
206
+ uom VARCHAR(10),
207
+ last_updated_at TIMESTAMP
208
+ )
209
+ LANGUAGE plpgsql
210
+ AS $$
211
+ BEGIN
212
+ RETURN QUERY
213
+ SELECT s.stock_id, s.location_id, s.catalogue_id, s.sku, s.batch_no,
214
+ s.qty_on_hand, s.qty_reserved, s.qty_available, s.uom, s.last_updated_at
215
+ FROM trans.scm_stock s
216
+ WHERE s.merchant_id = p_merchant_id
217
+ AND (p_location_id IS NULL OR s.location_id = p_location_id)
218
+ AND (p_sku IS NULL OR s.sku = p_sku)
219
+ ORDER BY s.sku, s.batch_no;
220
+ END;
221
+ $$;
222
+
223
+ -- Function to get stock ledger history with proper schema
224
+ CREATE OR REPLACE FUNCTION get_stock_ledger(
225
+ p_merchant_id VARCHAR(64),
226
+ p_sku VARCHAR(64) DEFAULT NULL,
227
+ p_batch_no VARCHAR(50) DEFAULT NULL,
228
+ p_limit INTEGER DEFAULT 100
229
+ ) RETURNS TABLE (
230
+ ledger_id UUID,
231
+ location_id VARCHAR(64),
232
+ catalogue_id UUID,
233
+ sku VARCHAR(64),
234
+ batch_no VARCHAR(50),
235
+ txn_type VARCHAR(30),
236
+ qty NUMERIC(14,3),
237
+ uom VARCHAR(10),
238
+ ref_type VARCHAR(30),
239
+ ref_id UUID,
240
+ ref_no VARCHAR(50),
241
+ remarks TEXT,
242
+ created_by VARCHAR(64),
243
+ created_at TIMESTAMP
244
+ )
245
+ LANGUAGE plpgsql
246
+ AS $$
247
+ BEGIN
248
+ RETURN QUERY
249
+ SELECT l.ledger_id, l.location_id, l.catalogue_id, l.sku, l.batch_no,
250
+ l.txn_type, l.qty, l.uom, l.ref_type, l.ref_id, l.ref_no,
251
+ l.remarks, l.created_by, l.created_at
252
+ FROM trans.scm_stock_ledger l
253
+ WHERE l.merchant_id = p_merchant_id
254
+ AND (p_sku IS NULL OR l.sku = p_sku)
255
+ AND (p_batch_no IS NULL OR l.batch_no = p_batch_no)
256
+ ORDER BY l.created_at DESC
257
+ LIMIT p_limit;
258
+ END;
259
+ $$;