MukeshKapoor25 commited on
Commit
9798804
·
1 Parent(s): 5ea6287

warehouse_id changes

Browse files
Files changed (33) hide show
  1. app/catalogues/controllers/router.py +3 -3
  2. app/catalogues/schemas/schema.py +5 -5
  3. app/catalogues/services/service.py +6 -6
  4. app/inventory/adjustments/controllers/router.py +2 -2
  5. app/inventory/adjustments/schemas/schema.py +6 -6
  6. app/inventory/adjustments/services/service.py +22 -22
  7. app/inventory/stock/controllers/router.py +8 -8
  8. app/inventory/stock/models/model.py +5 -5
  9. app/inventory/stock/schemas/schema.py +7 -7
  10. app/inventory/stock/services/service.py +66 -66
  11. app/inventory/stock_take/controllers/router.py +7 -7
  12. app/inventory/stock_take/schemas/approval_schema.py +3 -3
  13. app/inventory/stock_take/schemas/schema.py +3 -3
  14. app/inventory/stock_take/services/approval_service.py +4 -4
  15. app/inventory/stock_take/services/service.py +13 -13
  16. app/purchases/orders/services/service.py +1 -1
  17. app/sql/apply_bulk_stock_movements.sql +1 -1
  18. app/sql/apply_stock_movement.sql +7 -7
  19. app/sql/fn_get_po_items_for_order_process.sql +1 -1
  20. app/sql/fn_get_po_items_for_purchase_return.sql +1 -1
  21. app/sql/get_stock_ledger.sql +2 -2
  22. app/sql/get_stock_summary.sql +4 -4
  23. app/sql/release_stock_reservation.sql +2 -2
  24. app/sql/reserve_stock.sql +3 -3
  25. app/trade_sales/services/service.py +1 -1
  26. app/transports/controllers/router.py +3 -3
  27. app/transports/models/model.py +2 -2
  28. app/transports/schemas/schema.py +5 -5
  29. app/transports/services/service.py +13 -13
  30. app/utils/stock_utils.py +10 -10
  31. docs/database/sql/stored_procedures/stock_management.sql +18 -18
  32. docs/database/sql/stored_procedures/stock_management_updated.sql +18 -18
  33. tests/test_merchant_catalogue_list.py +5 -5
app/catalogues/controllers/router.py CHANGED
@@ -571,7 +571,7 @@ async def merchant_stock_batches_list(
571
  # Auto-fill merchant_id from JWT
572
  merchant_id = current_user.merchant_id
573
  catalogue_id = payload.catalogue_id
574
- location_id = payload.location_id
575
  sku = payload.sku
576
  skip = payload.skip or 0
577
  limit = payload.limit or 100
@@ -590,7 +590,7 @@ async def merchant_stock_batches_list(
590
  result, count = await service.list_merchant_stock_batches(
591
  merchant_id=merchant_id,
592
  catalogue_id=catalogue_id,
593
- location_id=location_id,
594
  sku=sku,
595
  skip=skip,
596
  limit=limit,
@@ -602,7 +602,7 @@ async def merchant_stock_batches_list(
602
  extra={
603
  "merchant_id": merchant_id,
604
  "catalogue_id": catalogue_id,
605
- "location_id": location_id,
606
  "sku": sku,
607
  "count": count,
608
  "duration": time.time() - start_time
 
571
  # Auto-fill merchant_id from JWT
572
  merchant_id = current_user.merchant_id
573
  catalogue_id = payload.catalogue_id
574
+ warehouse_id = payload.warehouse_id
575
  sku = payload.sku
576
  skip = payload.skip or 0
577
  limit = payload.limit or 100
 
590
  result, count = await service.list_merchant_stock_batches(
591
  merchant_id=merchant_id,
592
  catalogue_id=catalogue_id,
593
+ warehouse_id=warehouse_id,
594
  sku=sku,
595
  skip=skip,
596
  limit=limit,
 
602
  extra={
603
  "merchant_id": merchant_id,
604
  "catalogue_id": catalogue_id,
605
+ "warehouse_id": warehouse_id,
606
  "sku": sku,
607
  "count": count,
608
  "duration": time.time() - start_time
app/catalogues/schemas/schema.py CHANGED
@@ -1062,7 +1062,7 @@ class MerchantStockBatchesFilter(BaseModel):
1062
  """Merchant stock batches list request with PostgreSQL data from scm_stock"""
1063
  merchant_id: Optional[str] = Field(None, description="Merchant ID (auto-filled from JWT)")
1064
  catalogue_id: Optional[str] = Field(None, description="Filter by catalogue ID")
1065
- location_id: Optional[str] = Field(None, description="Filter by warehouse/location ID")
1066
  sku: Optional[str] = Field(None, description="Filter by SKU")
1067
  skip: int = Field(0, ge=0, description="Number of records to skip")
1068
  limit: int = Field(100, ge=1, le=1000, description="Maximum number of records to return")
@@ -1080,7 +1080,7 @@ class MerchantStockBatchesFilter(BaseModel):
1080
  # Stock batch fields from scm_stock and catalogue_ref
1081
  allowed_fields = {
1082
  # Stock fields
1083
- 'stock_id', 'merchant_id', 'location_id', 'catalogue_id', 'sku',
1084
  'batch_no', 'expiry_date', 'qty_on_hand', 'qty_reserved', 'qty_available',
1085
  'cost_price', 'uom', 'created_at', 'updated_at',
1086
  # Catalogue fields
@@ -1099,7 +1099,7 @@ class MerchantStockBatchesFilter(BaseModel):
1099
  "example": {
1100
  "merchant_id": "mch_01HZQX5K3N2P8R6T4V9W",
1101
  "catalogue_id": "cat_01HZQX5K3N2P8R6T4V9W",
1102
- "location_id": "loc_warehouse_001",
1103
  "skip": 0,
1104
  "limit": 50,
1105
  "projection_list": [
@@ -1118,7 +1118,7 @@ class StockBatchResponse(BaseModel):
1118
  """Response schema for stock batch information"""
1119
  stock_id: str
1120
  merchant_id: str
1121
- location_id: str
1122
  catalogue_id: str
1123
  sku: str
1124
  batch_no: str
@@ -1143,7 +1143,7 @@ class StockBatchResponse(BaseModel):
1143
  "example": {
1144
  "stock_id": "stock_01HZQX5K3N2P8R6T4V9W",
1145
  "merchant_id": "mch_01HZQX5K3N2P8R6T4V9W",
1146
- "location_id": "loc_warehouse_001",
1147
  "catalogue_id": "cat_01HZQX5K3N2P8R6T4V9W",
1148
  "sku": "SHMP-500-001",
1149
  "batch_no": "BATCH-2024-001",
 
1062
  """Merchant stock batches list request with PostgreSQL data from scm_stock"""
1063
  merchant_id: Optional[str] = Field(None, description="Merchant ID (auto-filled from JWT)")
1064
  catalogue_id: Optional[str] = Field(None, description="Filter by catalogue ID")
1065
+ warehouse_id: Optional[str] = Field(None, description="Filter by warehouse/location ID")
1066
  sku: Optional[str] = Field(None, description="Filter by SKU")
1067
  skip: int = Field(0, ge=0, description="Number of records to skip")
1068
  limit: int = Field(100, ge=1, le=1000, description="Maximum number of records to return")
 
1080
  # Stock batch fields from scm_stock and catalogue_ref
1081
  allowed_fields = {
1082
  # Stock fields
1083
+ 'stock_id', 'merchant_id', 'warehouse_id', 'catalogue_id', 'sku',
1084
  'batch_no', 'expiry_date', 'qty_on_hand', 'qty_reserved', 'qty_available',
1085
  'cost_price', 'uom', 'created_at', 'updated_at',
1086
  # Catalogue fields
 
1099
  "example": {
1100
  "merchant_id": "mch_01HZQX5K3N2P8R6T4V9W",
1101
  "catalogue_id": "cat_01HZQX5K3N2P8R6T4V9W",
1102
+ "warehouse_id": "loc_warehouse_001",
1103
  "skip": 0,
1104
  "limit": 50,
1105
  "projection_list": [
 
1118
  """Response schema for stock batch information"""
1119
  stock_id: str
1120
  merchant_id: str
1121
+ warehouse_id: str
1122
  catalogue_id: str
1123
  sku: str
1124
  batch_no: str
 
1143
  "example": {
1144
  "stock_id": "stock_01HZQX5K3N2P8R6T4V9W",
1145
  "merchant_id": "mch_01HZQX5K3N2P8R6T4V9W",
1146
+ "warehouse_id": "loc_warehouse_001",
1147
  "catalogue_id": "cat_01HZQX5K3N2P8R6T4V9W",
1148
  "sku": "SHMP-500-001",
1149
  "batch_no": "BATCH-2024-001",
app/catalogues/services/service.py CHANGED
@@ -1004,7 +1004,7 @@ class CatalogueService:
1004
  self,
1005
  merchant_id: str,
1006
  catalogue_id: str = None,
1007
- location_id: str = None,
1008
  sku: str = None,
1009
  skip: int = 0,
1010
  limit: int = 100,
@@ -1043,7 +1043,7 @@ class CatalogueService:
1043
  SELECT
1044
  s.stock_id,
1045
  s.merchant_id,
1046
- s.location_id,
1047
  s.catalogue_id,
1048
  s.sku,
1049
  s.batch_no,
@@ -1076,9 +1076,9 @@ class CatalogueService:
1076
  where_conditions.append("s.catalogue_id = :catalogue_id")
1077
  params["catalogue_id"] = catalogue_id
1078
 
1079
- if location_id:
1080
- where_conditions.append("s.location_id = :location_id")
1081
- params["location_id"] = location_id
1082
 
1083
  if sku:
1084
  where_conditions.append("s.sku = :sku")
@@ -1108,7 +1108,7 @@ class CatalogueService:
1108
  batch_dict = {
1109
  "stock_id": str(row.stock_id),
1110
  "merchant_id": row.merchant_id,
1111
- "location_id": row.location_id,
1112
  "catalogue_id": str(row.catalogue_id),
1113
  "sku": row.sku,
1114
  "batch_no": row.batch_no,
 
1004
  self,
1005
  merchant_id: str,
1006
  catalogue_id: str = None,
1007
+ warehouse_id: str = None,
1008
  sku: str = None,
1009
  skip: int = 0,
1010
  limit: int = 100,
 
1043
  SELECT
1044
  s.stock_id,
1045
  s.merchant_id,
1046
+ s.warehouse_id,
1047
  s.catalogue_id,
1048
  s.sku,
1049
  s.batch_no,
 
1076
  where_conditions.append("s.catalogue_id = :catalogue_id")
1077
  params["catalogue_id"] = catalogue_id
1078
 
1079
+ if warehouse_id:
1080
+ where_conditions.append("s.warehouse_id = :warehouse_id")
1081
+ params["warehouse_id"] = warehouse_id
1082
 
1083
  if sku:
1084
  where_conditions.append("s.sku = :sku")
 
1108
  batch_dict = {
1109
  "stock_id": str(row.stock_id),
1110
  "merchant_id": row.merchant_id,
1111
+ "warehouse_id": row.warehouse_id,
1112
  "catalogue_id": str(row.catalogue_id),
1113
  "sku": row.sku,
1114
  "batch_no": row.batch_no,
app/inventory/adjustments/controllers/router.py CHANGED
@@ -187,7 +187,7 @@ async def list_stock_adjustments(
187
  **Request Body:**
188
  - **filters**: Filter criteria (optional)
189
  - merchant_id: Filter by merchant
190
- - location_id: Filter by location
191
  - status: Filter by status (pending, approved, rejected)
192
  - adjustment_number: Search by adjustment number (partial match)
193
  - **skip**: Number of records to skip (pagination)
@@ -196,7 +196,7 @@ async def list_stock_adjustments(
196
 
197
  **Projection Examples:**
198
  - Basic info: ["adjustment_master_id", "adjustment_number", "status", "total_items"]
199
- - Summary: ["adjustment_number", "location_id", "total_items", "total_value", "status"]
200
  - Full details: null (returns all fields)
201
 
202
  **Filter Examples:**
 
187
  **Request Body:**
188
  - **filters**: Filter criteria (optional)
189
  - merchant_id: Filter by merchant
190
+ - warehouse_id: Filter by location
191
  - status: Filter by status (pending, approved, rejected)
192
  - adjustment_number: Search by adjustment number (partial match)
193
  - **skip**: Number of records to skip (pagination)
 
196
 
197
  **Projection Examples:**
198
  - Basic info: ["adjustment_master_id", "adjustment_number", "status", "total_items"]
199
+ - Summary: ["adjustment_number", "warehouse_id", "total_items", "total_value", "status"]
200
  - Full details: null (returns all fields)
201
 
202
  **Filter Examples:**
app/inventory/adjustments/schemas/schema.py CHANGED
@@ -42,7 +42,7 @@ class CreateStockAdjustmentRequest(BaseModel):
42
  class CreateStockAdjustmentRequestSingle(BaseModel):
43
  """Request schema for creating single stock adjustment (legacy support)"""
44
  merchant_id: str = Field(..., description="Merchant ID")
45
- location_id: str = Field(..., description="Location ID")
46
  sku: str = Field(..., description="SKU code")
47
  batch_no: Optional[str] = Field(None, description="Batch number")
48
  adj_type: AdjustmentType = Field(..., description="Adjustment type")
@@ -85,7 +85,7 @@ class StockAdjustmentResponse(BaseModel):
85
  """Response schema for stock adjustment (single line item)"""
86
  adjustment_id: str
87
  merchant_id: str
88
- location_id: str
89
  sku: str
90
  batch_no: Optional[str]
91
  adj_type: AdjustmentType
@@ -122,7 +122,7 @@ class StockAdjustmentMasterResponse(BaseModel):
122
  adjustment_master_id: str
123
  adjustment_number: str
124
  merchant_id: str
125
- location_id: str
126
  adjustment_date: datetime
127
  description: Optional[str]
128
  additional_notes: Optional[str]
@@ -147,7 +147,7 @@ class StockTakeModel(BaseModel):
147
  """Stock take model"""
148
  stock_take_id: str = Field(..., description="Unique stock take identifier")
149
  merchant_id: str = Field(..., description="Merchant ID")
150
- location_id: str = Field(..., description="Location ID")
151
  sku: str = Field(..., description="SKU code")
152
  batch_no: Optional[str] = Field(None, description="Batch number")
153
  system_qty: int = Field(..., ge=0, description="System quantity")
@@ -177,7 +177,7 @@ class CreateStockTakeRequest(BaseModel):
177
  class CreateStockTakeRequestSingle(BaseModel):
178
  """Request schema for creating single stock take (legacy support)"""
179
  merchant_id: str = Field(..., description="Merchant ID")
180
- location_id: str = Field(..., description="Location ID")
181
  sku: str = Field(..., description="SKU code")
182
  batch_no: Optional[str] = Field(None, description="Batch number")
183
  system_qty: int = Field(..., ge=0, description="System quantity")
@@ -194,7 +194,7 @@ class StockTakeResponse(BaseModel):
194
  """Response schema for stock take"""
195
  stock_take_id: str
196
  merchant_id: str
197
- location_id: str
198
  sku: str
199
  batch_no: Optional[str]
200
  system_qty: int
 
42
  class CreateStockAdjustmentRequestSingle(BaseModel):
43
  """Request schema for creating single stock adjustment (legacy support)"""
44
  merchant_id: str = Field(..., description="Merchant ID")
45
+ warehouse_id: str = Field(..., description="Location ID")
46
  sku: str = Field(..., description="SKU code")
47
  batch_no: Optional[str] = Field(None, description="Batch number")
48
  adj_type: AdjustmentType = Field(..., description="Adjustment type")
 
85
  """Response schema for stock adjustment (single line item)"""
86
  adjustment_id: str
87
  merchant_id: str
88
+ warehouse_id: str
89
  sku: str
90
  batch_no: Optional[str]
91
  adj_type: AdjustmentType
 
122
  adjustment_master_id: str
123
  adjustment_number: str
124
  merchant_id: str
125
+ warehouse_id: str
126
  adjustment_date: datetime
127
  description: Optional[str]
128
  additional_notes: Optional[str]
 
147
  """Stock take model"""
148
  stock_take_id: str = Field(..., description="Unique stock take identifier")
149
  merchant_id: str = Field(..., description="Merchant ID")
150
+ warehouse_id: str = Field(..., description="Location ID")
151
  sku: str = Field(..., description="SKU code")
152
  batch_no: Optional[str] = Field(None, description="Batch number")
153
  system_qty: int = Field(..., ge=0, description="System quantity")
 
177
  class CreateStockTakeRequestSingle(BaseModel):
178
  """Request schema for creating single stock take (legacy support)"""
179
  merchant_id: str = Field(..., description="Merchant ID")
180
+ warehouse_id: str = Field(..., description="Location ID")
181
  sku: str = Field(..., description="SKU code")
182
  batch_no: Optional[str] = Field(None, description="Batch number")
183
  system_qty: int = Field(..., ge=0, description="System quantity")
 
194
  """Response schema for stock take"""
195
  stock_take_id: str
196
  merchant_id: str
197
+ warehouse_id: str
198
  sku: str
199
  batch_no: Optional[str]
200
  system_qty: int
app/inventory/adjustments/services/service.py CHANGED
@@ -42,7 +42,7 @@ class StockAdjustmentService:
42
  @staticmethod
43
  async def _validate_stock_availability(
44
  merchant_id: str,
45
- location_id: str,
46
  sku: str,
47
  batch_no: Optional[str],
48
  qty: Decimal
@@ -53,7 +53,7 @@ class StockAdjustmentService:
53
  query = select(ScmStock).where(
54
  and_(
55
  ScmStock.merchant_id == merchant_id,
56
- ScmStock.location_id == location_id,
57
  ScmStock.sku == sku
58
  )
59
  )
@@ -93,7 +93,7 @@ class StockAdjustmentService:
93
  async def _create_ledger_entry(
94
  session,
95
  merchant_id: str,
96
- location_id: str,
97
  sku: str,
98
  batch_no: Optional[str],
99
  txn_type: TransactionType,
@@ -106,7 +106,7 @@ class StockAdjustmentService:
106
  try:
107
  ledger_entry = ScmStockLedger(
108
  merchant_id=merchant_id,
109
- location_id=location_id,
110
  sku=sku,
111
  batch_no=batch_no,
112
  txn_type=txn_type.value,
@@ -133,7 +133,7 @@ class StockAdjustmentService:
133
  async def _update_stock_snapshot(
134
  session,
135
  merchant_id: str,
136
- location_id: str,
137
  sku: str,
138
  batch_no: Optional[str],
139
  qty_change: Decimal
@@ -143,7 +143,7 @@ class StockAdjustmentService:
143
  query = select(ScmStock).where(
144
  and_(
145
  ScmStock.merchant_id == merchant_id,
146
- ScmStock.location_id == location_id,
147
  ScmStock.sku == sku
148
  )
149
  )
@@ -163,7 +163,7 @@ class StockAdjustmentService:
163
  else:
164
  stock = ScmStock(
165
  merchant_id=merchant_id,
166
- location_id=location_id,
167
  sku=sku,
168
  batch_no=batch_no,
169
  qty_on_hand=max(Decimal('0'), qty_change),
@@ -205,7 +205,7 @@ class StockAdjustmentService:
205
  master = ScmStockAdjustmentMaster(
206
  adjustment_number=adjustment_number,
207
  merchant_id=effective_merchant_id,
208
- location_id=payload.warehouse_id,
209
  adjustment_date=payload.adjustment_date or datetime.utcnow(),
210
  description=f"Stock adjustment session - {len(payload.entries)} items",
211
  additional_notes=payload.additional_notes or "Created via API",
@@ -261,7 +261,7 @@ class StockAdjustmentService:
261
  stock_query = select(ScmStock).where(
262
  and_(
263
  ScmStock.merchant_id == effective_merchant_id,
264
- ScmStock.location_id == payload.warehouse_id,
265
  ScmStock.sku == entry.sku
266
  )
267
  )
@@ -299,7 +299,7 @@ class StockAdjustmentService:
299
  results.append(StockAdjustmentResponse(
300
  adjustment_id=str(detail.adjustment_detail_id),
301
  merchant_id=master.merchant_id,
302
- location_id=master.location_id,
303
  sku=detail.sku,
304
  batch_no=detail.batch_no,
305
  adj_type=AdjustmentType(detail.adj_type),
@@ -358,7 +358,7 @@ class StockAdjustmentService:
358
  if payload.adj_type in [AdjustmentType.DAMAGE, AdjustmentType.EXPIRED, AdjustmentType.SHRINKAGE]:
359
  await StockAdjustmentService._validate_stock_availability(
360
  payload.merchant_id,
361
- payload.location_id,
362
  payload.sku,
363
  payload.batch_no,
364
  qty_decimal
@@ -377,7 +377,7 @@ class StockAdjustmentService:
377
  master = ScmStockAdjustmentMaster(
378
  adjustment_number=adjustment_number,
379
  merchant_id=payload.merchant_id,
380
- location_id=payload.location_id,
381
  adjustment_date=datetime.utcnow(),
382
  description=f"Single adjustment - {payload.sku}",
383
  status="pending",
@@ -393,7 +393,7 @@ class StockAdjustmentService:
393
  stock_query = select(ScmStock).where(
394
  and_(
395
  ScmStock.merchant_id == payload.merchant_id,
396
- ScmStock.location_id == payload.location_id,
397
  ScmStock.sku == payload.sku
398
  )
399
  )
@@ -447,7 +447,7 @@ class StockAdjustmentService:
447
  return StockAdjustmentResponse(
448
  adjustment_id=str(detail.adjustment_detail_id),
449
  merchant_id=master.merchant_id,
450
- location_id=master.location_id,
451
  sku=detail.sku,
452
  batch_no=detail.batch_no,
453
  adj_type=AdjustmentType(detail.adj_type),
@@ -492,7 +492,7 @@ class StockAdjustmentService:
492
  stock_query = select(ScmStock).where(
493
  and_(
494
  ScmStock.merchant_id == master.merchant_id,
495
- ScmStock.location_id == master.location_id,
496
  ScmStock.sku == detail.sku,
497
  ScmStock.batch_no == detail.batch_no
498
  )
@@ -512,7 +512,7 @@ class StockAdjustmentService:
512
  # Create stock transaction
513
  stock_transaction = StockTransaction(
514
  merchant_id=master.merchant_id,
515
- location_id=master.location_id,
516
  catalogue_id=catalogue_id,
517
  sku=detail.sku,
518
  batch_no=detail.batch_no,
@@ -625,7 +625,7 @@ class StockAdjustmentService:
625
  return StockAdjustmentResponse(
626
  adjustment_id=str(detail.adjustment_detail_id),
627
  merchant_id=master.merchant_id,
628
- location_id=master.location_id,
629
  sku=detail.sku,
630
  batch_no=detail.batch_no,
631
  adj_type=AdjustmentType(detail.adj_type),
@@ -738,7 +738,7 @@ class StockAdjustmentService:
738
  return StockAdjustmentResponse(
739
  adjustment_id=str(detail.adjustment_detail_id),
740
  merchant_id=master.merchant_id,
741
- location_id=master.location_id,
742
  sku=detail.sku,
743
  batch_no=detail.batch_no,
744
  adj_type=AdjustmentType(detail.adj_type),
@@ -851,7 +851,7 @@ class StockAdjustmentService:
851
  return StockAdjustmentResponse(
852
  adjustment_id=str(detail.adjustment_detail_id),
853
  merchant_id=master.merchant_id,
854
- location_id=master.location_id,
855
  sku=detail.sku,
856
  batch_no=detail.batch_no,
857
  adj_type=AdjustmentType(detail.adj_type),
@@ -897,8 +897,8 @@ class StockAdjustmentService:
897
  elif filters.get("merchant_id"):
898
  conditions.append(ScmStockAdjustmentMaster.merchant_id == filters["merchant_id"])
899
 
900
- if filters.get("location_id"):
901
- conditions.append(ScmStockAdjustmentMaster.location_id == filters["location_id"])
902
  if filters.get("status"):
903
  conditions.append(ScmStockAdjustmentMaster.status == filters["status"])
904
  if filters.get("adjustment_number"):
@@ -941,7 +941,7 @@ class StockAdjustmentService:
941
  adjustment_master_id=str(master.adjustment_master_id),
942
  adjustment_number=master.adjustment_number,
943
  merchant_id=master.merchant_id,
944
- location_id=master.location_id,
945
  adjustment_date=master.adjustment_date,
946
  description=master.description,
947
  additional_notes=master.additional_notes,
 
42
  @staticmethod
43
  async def _validate_stock_availability(
44
  merchant_id: str,
45
+ warehouse_id: str,
46
  sku: str,
47
  batch_no: Optional[str],
48
  qty: Decimal
 
53
  query = select(ScmStock).where(
54
  and_(
55
  ScmStock.merchant_id == merchant_id,
56
+ ScmStock.warehouse_id == warehouse_id,
57
  ScmStock.sku == sku
58
  )
59
  )
 
93
  async def _create_ledger_entry(
94
  session,
95
  merchant_id: str,
96
+ warehouse_id: str,
97
  sku: str,
98
  batch_no: Optional[str],
99
  txn_type: TransactionType,
 
106
  try:
107
  ledger_entry = ScmStockLedger(
108
  merchant_id=merchant_id,
109
+ warehouse_id=warehouse_id,
110
  sku=sku,
111
  batch_no=batch_no,
112
  txn_type=txn_type.value,
 
133
  async def _update_stock_snapshot(
134
  session,
135
  merchant_id: str,
136
+ warehouse_id: str,
137
  sku: str,
138
  batch_no: Optional[str],
139
  qty_change: Decimal
 
143
  query = select(ScmStock).where(
144
  and_(
145
  ScmStock.merchant_id == merchant_id,
146
+ ScmStock.warehouse_id == warehouse_id,
147
  ScmStock.sku == sku
148
  )
149
  )
 
163
  else:
164
  stock = ScmStock(
165
  merchant_id=merchant_id,
166
+ warehouse_id=warehouse_id,
167
  sku=sku,
168
  batch_no=batch_no,
169
  qty_on_hand=max(Decimal('0'), qty_change),
 
205
  master = ScmStockAdjustmentMaster(
206
  adjustment_number=adjustment_number,
207
  merchant_id=effective_merchant_id,
208
+ warehouse_id=payload.warehouse_id,
209
  adjustment_date=payload.adjustment_date or datetime.utcnow(),
210
  description=f"Stock adjustment session - {len(payload.entries)} items",
211
  additional_notes=payload.additional_notes or "Created via API",
 
261
  stock_query = select(ScmStock).where(
262
  and_(
263
  ScmStock.merchant_id == effective_merchant_id,
264
+ ScmStock.warehouse_id == payload.warehouse_id,
265
  ScmStock.sku == entry.sku
266
  )
267
  )
 
299
  results.append(StockAdjustmentResponse(
300
  adjustment_id=str(detail.adjustment_detail_id),
301
  merchant_id=master.merchant_id,
302
+ warehouse_id=master.warehouse_id,
303
  sku=detail.sku,
304
  batch_no=detail.batch_no,
305
  adj_type=AdjustmentType(detail.adj_type),
 
358
  if payload.adj_type in [AdjustmentType.DAMAGE, AdjustmentType.EXPIRED, AdjustmentType.SHRINKAGE]:
359
  await StockAdjustmentService._validate_stock_availability(
360
  payload.merchant_id,
361
+ payload.warehouse_id,
362
  payload.sku,
363
  payload.batch_no,
364
  qty_decimal
 
377
  master = ScmStockAdjustmentMaster(
378
  adjustment_number=adjustment_number,
379
  merchant_id=payload.merchant_id,
380
+ warehouse_id=payload.warehouse_id,
381
  adjustment_date=datetime.utcnow(),
382
  description=f"Single adjustment - {payload.sku}",
383
  status="pending",
 
393
  stock_query = select(ScmStock).where(
394
  and_(
395
  ScmStock.merchant_id == payload.merchant_id,
396
+ ScmStock.warehouse_id == payload.warehouse_id,
397
  ScmStock.sku == payload.sku
398
  )
399
  )
 
447
  return StockAdjustmentResponse(
448
  adjustment_id=str(detail.adjustment_detail_id),
449
  merchant_id=master.merchant_id,
450
+ warehouse_id=master.warehouse_id,
451
  sku=detail.sku,
452
  batch_no=detail.batch_no,
453
  adj_type=AdjustmentType(detail.adj_type),
 
492
  stock_query = select(ScmStock).where(
493
  and_(
494
  ScmStock.merchant_id == master.merchant_id,
495
+ ScmStock.warehouse_id == master.warehouse_id,
496
  ScmStock.sku == detail.sku,
497
  ScmStock.batch_no == detail.batch_no
498
  )
 
512
  # Create stock transaction
513
  stock_transaction = StockTransaction(
514
  merchant_id=master.merchant_id,
515
+ warehouse_id=master.warehouse_id,
516
  catalogue_id=catalogue_id,
517
  sku=detail.sku,
518
  batch_no=detail.batch_no,
 
625
  return StockAdjustmentResponse(
626
  adjustment_id=str(detail.adjustment_detail_id),
627
  merchant_id=master.merchant_id,
628
+ warehouse_id=master.warehouse_id,
629
  sku=detail.sku,
630
  batch_no=detail.batch_no,
631
  adj_type=AdjustmentType(detail.adj_type),
 
738
  return StockAdjustmentResponse(
739
  adjustment_id=str(detail.adjustment_detail_id),
740
  merchant_id=master.merchant_id,
741
+ warehouse_id=master.warehouse_id,
742
  sku=detail.sku,
743
  batch_no=detail.batch_no,
744
  adj_type=AdjustmentType(detail.adj_type),
 
851
  return StockAdjustmentResponse(
852
  adjustment_id=str(detail.adjustment_detail_id),
853
  merchant_id=master.merchant_id,
854
+ warehouse_id=master.warehouse_id,
855
  sku=detail.sku,
856
  batch_no=detail.batch_no,
857
  adj_type=AdjustmentType(detail.adj_type),
 
897
  elif filters.get("merchant_id"):
898
  conditions.append(ScmStockAdjustmentMaster.merchant_id == filters["merchant_id"])
899
 
900
+ if filters.get("warehouse_id"):
901
+ conditions.append(ScmStockAdjustmentMaster.warehouse_id == filters["warehouse_id"])
902
  if filters.get("status"):
903
  conditions.append(ScmStockAdjustmentMaster.status == filters["status"])
904
  if filters.get("adjustment_number"):
 
941
  adjustment_master_id=str(master.adjustment_master_id),
942
  adjustment_number=master.adjustment_number,
943
  merchant_id=master.merchant_id,
944
+ warehouse_id=master.warehouse_id,
945
  adjustment_date=master.adjustment_date,
946
  description=master.description,
947
  additional_notes=master.additional_notes,
app/inventory/stock/controllers/router.py CHANGED
@@ -163,7 +163,7 @@ async def get_stock_summary(
163
  service = StockService(pg_session)
164
  result = await service.get_stock_summary_with_projection(
165
  merchant_id=current_user.merchant_id,
166
- location_id=payload.location_id,
167
  sku=payload.sku,
168
  catalogue_id=payload.catalogue_id,
169
  projection_list=payload.projection_list
@@ -213,7 +213,7 @@ async def get_stock_valuation(
213
  service = StockService(pg_session)
214
  result = await service.get_stock_valuation(
215
  merchant_id=current_user.merchant_id,
216
- location_id=payload.location_id,
217
  as_of_date=payload.as_of_date,
218
  projection_list=payload.projection_list
219
  )
@@ -238,7 +238,7 @@ async def reserve_stock(
238
  service = StockService(pg_session)
239
  success = await service.reserve_stock(
240
  merchant_id=current_user.merchant_id,
241
- location_id=payload.location_id,
242
  sku=payload.sku,
243
  batch_no=payload.batch_no,
244
  qty=payload.qty,
@@ -269,7 +269,7 @@ async def release_stock_reservation(
269
  service = StockService(pg_session)
270
  success = await service.release_stock_reservation(
271
  merchant_id=current_user.merchant_id,
272
- location_id=payload.location_id,
273
  sku=payload.sku,
274
  batch_no=payload.batch_no,
275
  qty=payload.qty,
@@ -305,7 +305,7 @@ async def adjust_stock(
305
 
306
  transaction_id, success = await service.process_stock_adjustment(
307
  merchant_id=current_user.merchant_id,
308
- location_id=payload.location_id,
309
  sku=payload.sku,
310
  batch_no=payload.batch_no,
311
  adjustment_qty=payload.adjustment_qty,
@@ -351,7 +351,7 @@ async def bulk_adjust_stock(
351
  ref_id = f"{bulk_ref_id}_{adjustment.sku}_{adjustment.batch_no}"
352
  transaction_id, success = await service.process_stock_adjustment(
353
  merchant_id=current_user.merchant_id,
354
- location_id=adjustment.location_id,
355
  sku=adjustment.sku,
356
  batch_no=adjustment.batch_no,
357
  adjustment_qty=adjustment.adjustment_qty,
@@ -412,8 +412,8 @@ async def transfer_stock(
412
 
413
  success = await service.process_stock_transfer(
414
  merchant_id=current_user.merchant_id,
415
- from_location_id=payload.from_location_id,
416
- to_location_id=payload.to_location_id,
417
  sku=payload.sku,
418
  batch_no=payload.batch_no,
419
  qty=payload.qty,
 
163
  service = StockService(pg_session)
164
  result = await service.get_stock_summary_with_projection(
165
  merchant_id=current_user.merchant_id,
166
+ warehouse_id=payload.warehouse_id,
167
  sku=payload.sku,
168
  catalogue_id=payload.catalogue_id,
169
  projection_list=payload.projection_list
 
213
  service = StockService(pg_session)
214
  result = await service.get_stock_valuation(
215
  merchant_id=current_user.merchant_id,
216
+ warehouse_id=payload.warehouse_id,
217
  as_of_date=payload.as_of_date,
218
  projection_list=payload.projection_list
219
  )
 
238
  service = StockService(pg_session)
239
  success = await service.reserve_stock(
240
  merchant_id=current_user.merchant_id,
241
+ warehouse_id=payload.warehouse_id,
242
  sku=payload.sku,
243
  batch_no=payload.batch_no,
244
  qty=payload.qty,
 
269
  service = StockService(pg_session)
270
  success = await service.release_stock_reservation(
271
  merchant_id=current_user.merchant_id,
272
+ warehouse_id=payload.warehouse_id,
273
  sku=payload.sku,
274
  batch_no=payload.batch_no,
275
  qty=payload.qty,
 
305
 
306
  transaction_id, success = await service.process_stock_adjustment(
307
  merchant_id=current_user.merchant_id,
308
+ warehouse_id=payload.warehouse_id,
309
  sku=payload.sku,
310
  batch_no=payload.batch_no,
311
  adjustment_qty=payload.adjustment_qty,
 
351
  ref_id = f"{bulk_ref_id}_{adjustment.sku}_{adjustment.batch_no}"
352
  transaction_id, success = await service.process_stock_adjustment(
353
  merchant_id=current_user.merchant_id,
354
+ warehouse_id=adjustment.warehouse_id,
355
  sku=adjustment.sku,
356
  batch_no=adjustment.batch_no,
357
  adjustment_qty=adjustment.adjustment_qty,
 
412
 
413
  success = await service.process_stock_transfer(
414
  merchant_id=current_user.merchant_id,
415
+ from_warehouse_id=payload.from_warehouse_id,
416
+ to_warehouse_id=payload.to_warehouse_id,
417
  sku=payload.sku,
418
  batch_no=payload.batch_no,
419
  qty=payload.qty,
app/inventory/stock/models/model.py CHANGED
@@ -22,7 +22,7 @@ class ScmStock(Base):
22
 
23
  stock_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
24
  merchant_id = Column(String(64), nullable=False)
25
- location_id = Column(String(64), nullable=False)
26
  sku = Column(String(64), nullable=False)
27
  batch_no = Column(String(50), nullable=True)
28
 
@@ -53,7 +53,7 @@ class ScmStockLedger(Base):
53
 
54
  ledger_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
55
  merchant_id = Column(String(64), nullable=False)
56
- location_id = Column(String(64), nullable=False)
57
  sku = Column(String(64), nullable=False)
58
  batch_no = Column(String(50))
59
 
@@ -83,7 +83,7 @@ class ScmStockAdjustmentMaster(Base):
83
  adjustment_master_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
84
  adjustment_number = Column(String(50), nullable=False) # Human readable: ADJ-2024-001
85
  merchant_id = Column(String(64), nullable=False)
86
- location_id = Column(String(64), nullable=False)
87
 
88
  # Document level info
89
  adjustment_date = Column(TIMESTAMP(timezone=True), nullable=False, default=datetime.utcnow)
@@ -174,7 +174,7 @@ class ScmStockTakeMaster(Base):
174
  stock_take_master_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
175
  stock_take_number = Column(String(50), nullable=False) # Human readable: ST-2024-001
176
  merchant_id = Column(String(64), nullable=False)
177
- location_id = Column(String(64), nullable=False)
178
 
179
  # Document level info
180
  stock_take_date = Column(TIMESTAMP(timezone=True), nullable=False)
@@ -272,7 +272,7 @@ class ScmStockAdjustment(Base):
272
 
273
  adjustment_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
274
  merchant_id = Column(String(64), nullable=False)
275
- location_id = Column(String(64), nullable=False)
276
  sku = Column(String(64), nullable=False)
277
  batch_no = Column(String(50))
278
 
 
22
 
23
  stock_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
24
  merchant_id = Column(String(64), nullable=False)
25
+ warehouse_id = Column(String(64), nullable=False)
26
  sku = Column(String(64), nullable=False)
27
  batch_no = Column(String(50), nullable=True)
28
 
 
53
 
54
  ledger_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
55
  merchant_id = Column(String(64), nullable=False)
56
+ warehouse_id = Column(String(64), nullable=False)
57
  sku = Column(String(64), nullable=False)
58
  batch_no = Column(String(50))
59
 
 
83
  adjustment_master_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
84
  adjustment_number = Column(String(50), nullable=False) # Human readable: ADJ-2024-001
85
  merchant_id = Column(String(64), nullable=False)
86
+ warehouse_id = Column(String(64), nullable=False)
87
 
88
  # Document level info
89
  adjustment_date = Column(TIMESTAMP(timezone=True), nullable=False, default=datetime.utcnow)
 
174
  stock_take_master_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
175
  stock_take_number = Column(String(50), nullable=False) # Human readable: ST-2024-001
176
  merchant_id = Column(String(64), nullable=False)
177
+ warehouse_id = Column(String(64), nullable=False)
178
 
179
  # Document level info
180
  stock_take_date = Column(TIMESTAMP(timezone=True), nullable=False)
 
272
 
273
  adjustment_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
274
  merchant_id = Column(String(64), nullable=False)
275
+ warehouse_id = Column(String(64), nullable=False)
276
  sku = Column(String(64), nullable=False)
277
  batch_no = Column(String(50))
278
 
app/inventory/stock/schemas/schema.py CHANGED
@@ -51,7 +51,7 @@ class StockAdjustmentListFilter(BaseModel):
51
 
52
  class StockReservationRequest(BaseModel):
53
  """Stock reservation request"""
54
- location_id: str = Field(..., description="Warehouse/location ID")
55
  sku: str = Field(..., description="SKU code")
56
  batch_no: str = Field(..., description="Batch number")
57
  qty: Decimal = Field(..., gt=0, description="Quantity to reserve")
@@ -60,7 +60,7 @@ class StockReservationRequest(BaseModel):
60
 
61
  class StockAdjustmentRequest(BaseModel):
62
  """Stock adjustment request"""
63
- location_id: str = Field(..., description="Warehouse/location ID")
64
  sku: str = Field(..., description="SKU code")
65
  batch_no: str = Field(..., description="Batch number")
66
  adjustment_qty: Decimal = Field(..., description="Adjustment quantity (positive for increase, negative for decrease)")
@@ -70,8 +70,8 @@ class StockAdjustmentRequest(BaseModel):
70
 
71
  class StockTransferRequest(BaseModel):
72
  """Stock transfer request"""
73
- from_location_id: str = Field(..., description="Source location ID")
74
- to_location_id: str = Field(..., description="Destination location ID")
75
  sku: str = Field(..., description="SKU code")
76
  batch_no: str = Field(..., description="Batch number")
77
  qty: Decimal = Field(..., gt=0, description="Quantity to transfer")
@@ -86,7 +86,7 @@ class BulkStockAdjustmentRequest(BaseModel):
86
 
87
  class StockSummaryRequest(BaseModel):
88
  """Stock summary request with projection support"""
89
- location_id: Optional[str] = Field(None, description="Filter by location")
90
  sku: Optional[str] = Field(None, description="Filter by SKU")
91
  catalogue_id: Optional[str] = Field(None, description="Filter by catalogue ID")
92
  projection_list: Optional[List[str]] = Field(
@@ -97,7 +97,7 @@ class StockSummaryRequest(BaseModel):
97
 
98
  class StockAvailabilityRequest(BaseModel):
99
  """Stock availability check request"""
100
- items: List[Dict[str, Any]] = Field(..., description="List of items to check (sku, qty, location_id)")
101
  projection_list: Optional[List[str]] = Field(
102
  None,
103
  description="List of fields to include in response"
@@ -106,7 +106,7 @@ class StockAvailabilityRequest(BaseModel):
106
 
107
  class StockValuationRequest(BaseModel):
108
  """Stock valuation request"""
109
- location_id: Optional[str] = Field(None, description="Filter by location")
110
  as_of_date: Optional[datetime] = Field(None, description="Valuation as of date")
111
  projection_list: Optional[List[str]] = Field(
112
  None,
 
51
 
52
  class StockReservationRequest(BaseModel):
53
  """Stock reservation request"""
54
+ warehouse_id: str = Field(..., description="Warehouse/location ID")
55
  sku: str = Field(..., description="SKU code")
56
  batch_no: str = Field(..., description="Batch number")
57
  qty: Decimal = Field(..., gt=0, description="Quantity to reserve")
 
60
 
61
  class StockAdjustmentRequest(BaseModel):
62
  """Stock adjustment request"""
63
+ warehouse_id: str = Field(..., description="Warehouse/location ID")
64
  sku: str = Field(..., description="SKU code")
65
  batch_no: str = Field(..., description="Batch number")
66
  adjustment_qty: Decimal = Field(..., description="Adjustment quantity (positive for increase, negative for decrease)")
 
70
 
71
  class StockTransferRequest(BaseModel):
72
  """Stock transfer request"""
73
+ from_warehouse_id: str = Field(..., description="Source location ID")
74
+ to_warehouse_id: str = Field(..., description="Destination location ID")
75
  sku: str = Field(..., description="SKU code")
76
  batch_no: str = Field(..., description="Batch number")
77
  qty: Decimal = Field(..., gt=0, description="Quantity to transfer")
 
86
 
87
  class StockSummaryRequest(BaseModel):
88
  """Stock summary request with projection support"""
89
+ warehouse_id: Optional[str] = Field(None, description="Filter by location")
90
  sku: Optional[str] = Field(None, description="Filter by SKU")
91
  catalogue_id: Optional[str] = Field(None, description="Filter by catalogue ID")
92
  projection_list: Optional[List[str]] = Field(
 
97
 
98
  class StockAvailabilityRequest(BaseModel):
99
  """Stock availability check request"""
100
+ items: List[Dict[str, Any]] = Field(..., description="List of items to check (sku, qty, warehouse_id)")
101
  projection_list: Optional[List[str]] = Field(
102
  None,
103
  description="List of fields to include in response"
 
106
 
107
  class StockValuationRequest(BaseModel):
108
  """Stock valuation request"""
109
+ warehouse_id: Optional[str] = Field(None, description="Filter by location")
110
  as_of_date: Optional[datetime] = Field(None, description="Valuation as of date")
111
  projection_list: Optional[List[str]] = Field(
112
  None,
app/inventory/stock/services/service.py CHANGED
@@ -43,7 +43,7 @@ class StockTransaction:
43
  def __init__(
44
  self,
45
  merchant_id: str,
46
- location_id: str,
47
  catalogue_id: str,
48
  sku: str,
49
  batch_no: str,
@@ -58,7 +58,7 @@ class StockTransaction:
58
  created_by: str = "system"
59
  ):
60
  self.merchant_id = merchant_id
61
- self.location_id = location_id
62
  self.catalogue_id = catalogue_id
63
  self.sku = sku
64
  self.batch_no = batch_no
@@ -93,14 +93,14 @@ class StockService:
93
  # # Call stored procedure with correct parameters (10 parameters as per procedure definition)
94
  # query = text("""
95
  # SELECT trans.apply_stock_movement(
96
- # :merchant_id, :location_id, :sku, :batch_no, :qty,
97
  # :txn_type, :ref_type, :ref_id, :ref_no, :created_by
98
  # )
99
  # """)
100
 
101
  # await self.db.execute(query, {
102
  # "merchant_id": transaction.merchant_id,
103
- # "location_id": transaction.location_id,
104
  # "sku": transaction.sku,
105
  # "batch_no": transaction.batch_no,
106
  # "qty": float(transaction.qty), # Convert Decimal to float for PostgreSQL NUMERIC
@@ -145,7 +145,7 @@ class StockService:
145
  for transaction in transactions:
146
  movement = {
147
  "merchant_id": transaction.merchant_id,
148
- "location_id": transaction.location_id,
149
  "sku": transaction.sku,
150
  "batch_no": transaction.batch_no,
151
  "catalogue_id": transaction.catalogue_id,
@@ -161,7 +161,7 @@ class StockService:
161
 
162
  # Call bulk stored procedure
163
  query = text("SELECT trans.apply_bulk_stock_movements(:movements) as results")
164
- logger.info(f"merchant_id: {transaction.merchant_id},location_id: {transaction.location_id},sku: {transaction.sku},batch_no: {transaction.batch_no},catalogue_id: {transaction.catalogue_id} ,uom: {transaction.uom}, expiry_date: {transaction.exp_dt} ,qty: {str(transaction.qty)},txn_type: {transaction.txn_type.value} ,ref_type: {transaction.ref_type.value} , ref_id: {str(transaction.ref_id)} ,created_by: {transaction.created_by}")
165
 
166
  result = await self.db.execute(query, {
167
  "movements": json.dumps(movements)
@@ -202,8 +202,8 @@ class StockService:
202
  if not transaction.merchant_id:
203
  raise ValueError("merchant_id is required")
204
 
205
- if not transaction.location_id:
206
- raise ValueError("location_id is required")
207
 
208
  if not transaction.sku:
209
  raise ValueError("sku is required")
@@ -255,7 +255,7 @@ class StockService:
255
  for item in grn_items:
256
  transaction = StockTransaction(
257
  merchant_id=item.merchant_id,
258
- location_id=item.wh_location or "default",
259
  catalogue_id=str(item.catalogue_id),
260
  sku=item.sku,
261
  batch_no=item.batch_no,
@@ -284,7 +284,7 @@ class StockService:
284
  async def process_stock_adjustment(
285
  self,
286
  merchant_id: str,
287
- location_id: str,
288
  sku: str,
289
  batch_no: str,
290
  adjustment_qty: Decimal,
@@ -305,13 +305,13 @@ class StockService:
305
  # Get catalogue_id and uom from existing stock
306
  stock_query = text("""
307
  SELECT catalogue_id, uom FROM trans.scm_stock
308
- WHERE merchant_id = :merchant_id AND location_id = :location_id
309
  AND sku = :sku AND batch_no = :batch_no
310
  """)
311
 
312
  result = await self.db.execute(stock_query, {
313
  "merchant_id": merchant_id,
314
- "location_id": location_id,
315
  "sku": sku,
316
  "batch_no": batch_no
317
  })
@@ -329,7 +329,7 @@ class StockService:
329
 
330
  transaction = StockTransaction(
331
  merchant_id=merchant_id,
332
- location_id=location_id,
333
  catalogue_id=catalogue_id,
334
  sku=sku,
335
  batch_no=batch_no,
@@ -352,15 +352,15 @@ class StockService:
352
  async def get_stock_summary(
353
  self,
354
  merchant_id: str,
355
- location_id: Optional[str] = None,
356
  sku: Optional[str] = None
357
  ) -> List[Dict[str, Any]]:
358
  """Get current stock summary using stored procedure"""
359
  try:
360
- query = text("SELECT * FROM get_stock_summary(:merchant_id, :location_id, :sku)")
361
  result = await self.db.execute(query, {
362
  "merchant_id": merchant_id,
363
- "location_id": location_id,
364
  "sku": sku
365
  })
366
 
@@ -369,7 +369,7 @@ class StockService:
369
  return [
370
  {
371
  "stock_id": str(stock.stock_id),
372
- "location_id": stock.location_id,
373
  "catalogue_id": str(stock.catalogue_id),
374
  "sku": stock.sku,
375
  "batch_no": stock.batch_no,
@@ -408,7 +408,7 @@ class StockService:
408
  return [
409
  {
410
  "ledger_id": str(entry.ledger_id),
411
- "location_id": entry.location_id,
412
  "catalogue_id": str(entry.catalogue_id),
413
  "sku": entry.sku,
414
  "batch_no": entry.batch_no,
@@ -441,7 +441,7 @@ class StockService:
441
  # Build base query
442
  base_query = """
443
  SELECT
444
- stock_id, merchant_id, location_id, catalogue_id, sku, batch_no,
445
  exp_dt, qty_on_hand, qty_reserved, qty_available, uom,
446
  created_at, updated_at, last_updated_at
447
  FROM trans.scm_stock
@@ -456,9 +456,9 @@ class StockService:
456
  if key == "merchant_id":
457
  where_conditions.append("merchant_id = :merchant_id")
458
  params["merchant_id"] = value
459
- elif key == "location_id":
460
- where_conditions.append("location_id = :location_id")
461
- params["location_id"] = value
462
  elif key == "sku":
463
  where_conditions.append("sku ILIKE :sku")
464
  params["sku"] = f"%{value}%"
@@ -494,7 +494,7 @@ class StockService:
494
  row_dict = {
495
  "stock_id": str(row.stock_id),
496
  "merchant_id": row.merchant_id,
497
- "location_id": row.location_id,
498
  "catalogue_id": str(row.catalogue_id),
499
  "sku": row.sku,
500
  "batch_no": row.batch_no,
@@ -541,7 +541,7 @@ class StockService:
541
  # Build base query
542
  base_query = """
543
  SELECT
544
- ledger_id, merchant_id, location_id, catalogue_id, sku, batch_no,
545
  exp_dt, qty, uom, txn_type, ref_type, ref_id, ref_no,
546
  remarks, created_by, created_at
547
  FROM trans.scm_stock_ledger
@@ -556,9 +556,9 @@ class StockService:
556
  if key == "merchant_id":
557
  where_conditions.append("merchant_id = :merchant_id")
558
  params["merchant_id"] = value
559
- elif key == "location_id":
560
- where_conditions.append("location_id = :location_id")
561
- params["location_id"] = value
562
  elif key == "sku":
563
  where_conditions.append("sku ILIKE :sku")
564
  params["sku"] = f"%{value}%"
@@ -594,7 +594,7 @@ class StockService:
594
  row_dict = {
595
  "ledger_id": str(row.ledger_id),
596
  "merchant_id": row.merchant_id,
597
- "location_id": row.location_id,
598
  "catalogue_id": str(row.catalogue_id),
599
  "sku": row.sku,
600
  "batch_no": row.batch_no,
@@ -643,7 +643,7 @@ class StockService:
643
  # Build base query
644
  base_query = """
645
  SELECT
646
- adjustment_id, merchant_id, location_id, catalogue_id, sku, batch_no,
647
  adjustment_qty, adjustment_type, reason, status, approved_by,
648
  approved_at, created_by, created_at, updated_at
649
  FROM trans.scm_stock_adjustment
@@ -658,9 +658,9 @@ class StockService:
658
  if key == "merchant_id":
659
  where_conditions.append("merchant_id = :merchant_id")
660
  params["merchant_id"] = value
661
- elif key == "location_id":
662
- where_conditions.append("location_id = :location_id")
663
- params["location_id"] = value
664
  elif key == "sku":
665
  where_conditions.append("sku ILIKE :sku")
666
  params["sku"] = f"%{value}%"
@@ -693,7 +693,7 @@ class StockService:
693
  row_dict = {
694
  "adjustment_id": str(row.adjustment_id),
695
  "merchant_id": row.merchant_id,
696
- "location_id": row.location_id,
697
  "catalogue_id": str(row.catalogue_id),
698
  "sku": row.sku,
699
  "batch_no": row.batch_no,
@@ -732,7 +732,7 @@ class StockService:
732
  async def reserve_stock(
733
  self,
734
  merchant_id: str,
735
- location_id: str,
736
  sku: str,
737
  batch_no: str,
738
  qty: Decimal,
@@ -742,12 +742,12 @@ class StockService:
742
  """Reserve stock using stored procedure"""
743
  try:
744
  query = text("""
745
- SELECT reserve_stock(:merchant_id, :location_id, :sku, :batch_no, :qty, :ref_id, :created_by)
746
  """)
747
 
748
  result = await self.db.execute(query, {
749
  "merchant_id": merchant_id,
750
- "location_id": location_id,
751
  "sku": sku,
752
  "batch_no": batch_no,
753
  "qty": float(qty),
@@ -769,7 +769,7 @@ class StockService:
769
  async def release_stock_reservation(
770
  self,
771
  merchant_id: str,
772
- location_id: str,
773
  sku: str,
774
  batch_no: str,
775
  qty: Decimal,
@@ -779,12 +779,12 @@ class StockService:
779
  """Release stock reservation using stored procedure"""
780
  try:
781
  query = text("""
782
- SELECT release_stock_reservation(:merchant_id, :location_id, :sku, :batch_no, :qty, :ref_id, :created_by)
783
  """)
784
 
785
  result = await self.db.execute(query, {
786
  "merchant_id": merchant_id,
787
- "location_id": location_id,
788
  "sku": sku,
789
  "batch_no": batch_no,
790
  "qty": float(qty),
@@ -806,7 +806,7 @@ class StockService:
806
  async def get_stock_summary_with_projection(
807
  self,
808
  merchant_id: str,
809
- location_id: Optional[str] = None,
810
  sku: Optional[str] = None,
811
  catalogue_id: Optional[str] = None,
812
  projection_list: Optional[List[str]] = None
@@ -814,17 +814,17 @@ class StockService:
814
  """Get stock summary with optional projection"""
815
  try:
816
  # Build query with optional filters
817
- query_parts = ["SELECT * FROM get_stock_summary(:merchant_id, :location_id, :sku)"]
818
  params = {
819
  "merchant_id": merchant_id,
820
- "location_id": location_id,
821
  "sku": sku
822
  }
823
 
824
  # If catalogue_id is provided, add it as a filter
825
  if catalogue_id:
826
  query_parts[0] = """
827
- SELECT s.* FROM get_stock_summary(:merchant_id, :location_id, :sku) s
828
  WHERE s.catalogue_id = :catalogue_id
829
  """
830
  params["catalogue_id"] = catalogue_id
@@ -837,7 +837,7 @@ class StockService:
837
  for stock in stocks:
838
  stock_dict = {
839
  "stock_id": str(stock.stock_id),
840
- "location_id": stock.location_id,
841
  "catalogue_id": str(stock.catalogue_id),
842
  "sku": stock.sku,
843
  "batch_no": stock.batch_no,
@@ -877,7 +877,7 @@ class StockService:
877
  for item in items:
878
  sku = item.get("sku")
879
  required_qty = item.get("qty", 0)
880
- location_id = item.get("location_id")
881
 
882
  if not sku:
883
  continue
@@ -885,20 +885,20 @@ class StockService:
885
  # Query available stock
886
  query = text("""
887
  SELECT
888
- sku, location_id, batch_no, qty_available, exp_dt,
889
  catalogue_id, uom
890
  FROM trans.scm_stock
891
  WHERE merchant_id = :merchant_id
892
  AND sku = :sku
893
  AND qty_available > 0
894
- AND (:location_id IS NULL OR location_id = :location_id)
895
  ORDER BY exp_dt ASC NULLS LAST, created_at ASC
896
  """)
897
 
898
  result = await self.db.execute(query, {
899
  "merchant_id": merchant_id,
900
  "sku": sku,
901
- "location_id": location_id
902
  })
903
 
904
  stock_rows = result.fetchall()
@@ -912,7 +912,7 @@ class StockService:
912
  "required_qty": required_qty,
913
  "total_available": total_available,
914
  "is_available": is_available,
915
- "location_id": location_id,
916
  "batches": [
917
  {
918
  "batch_no": row.batch_no,
@@ -920,7 +920,7 @@ class StockService:
920
  "exp_dt": row.exp_dt.isoformat() if row.exp_dt else None,
921
  "catalogue_id": str(row.catalogue_id),
922
  "uom": row.uom,
923
- "location_id": row.location_id
924
  }
925
  for row in stock_rows
926
  ]
@@ -945,7 +945,7 @@ class StockService:
945
  async def get_stock_valuation(
946
  self,
947
  merchant_id: str,
948
- location_id: Optional[str] = None,
949
  as_of_date: Optional[datetime] = None,
950
  projection_list: Optional[List[str]] = None
951
  ) -> List[Dict[str, Any]]:
@@ -954,7 +954,7 @@ class StockService:
954
  # Build query for stock valuation
955
  query = text("""
956
  SELECT
957
- s.stock_id, s.location_id, s.catalogue_id, s.sku, s.batch_no,
958
  s.qty_on_hand, s.uom, s.exp_dt,
959
  cr.mrp, cr.base_price,
960
  (s.qty_on_hand * COALESCE(cr.base_price, cr.mrp, 0)) as valuation_amount
@@ -962,14 +962,14 @@ class StockService:
962
  LEFT JOIN trans.catalogue_ref cr ON s.catalogue_id = cr.catalogue_id
963
  WHERE s.merchant_id = :merchant_id
964
  AND s.qty_on_hand > 0
965
- AND (:location_id IS NULL OR s.location_id = :location_id)
966
  AND (:as_of_date IS NULL OR s.created_at <= :as_of_date)
967
  ORDER BY s.sku, s.batch_no
968
  """)
969
 
970
  params = {
971
  "merchant_id": merchant_id,
972
- "location_id": location_id,
973
  "as_of_date": as_of_date
974
  }
975
 
@@ -986,7 +986,7 @@ class StockService:
986
 
987
  valuation_dict = {
988
  "stock_id": str(row.stock_id),
989
- "location_id": row.location_id,
990
  "catalogue_id": str(row.catalogue_id),
991
  "sku": row.sku,
992
  "batch_no": row.batch_no,
@@ -1032,7 +1032,7 @@ class StockService:
1032
  try:
1033
  query = text("""
1034
  SELECT
1035
- s.stock_id, s.merchant_id, s.location_id, s.catalogue_id, s.sku, s.batch_no,
1036
  s.exp_dt, s.qty_on_hand, s.qty_reserved, s.qty_available, s.uom,
1037
  s.created_at, s.updated_at, s.last_updated_at,
1038
  cr.catalogue_name, cr.mrp, cr.base_price
@@ -1053,7 +1053,7 @@ class StockService:
1053
  stock_dict = {
1054
  "stock_id": str(row.stock_id),
1055
  "merchant_id": row.merchant_id,
1056
- "location_id": row.location_id,
1057
  "catalogue_id": str(row.catalogue_id),
1058
  "sku": row.sku,
1059
  "batch_no": row.batch_no,
@@ -1092,8 +1092,8 @@ class StockService:
1092
  async def process_stock_transfer(
1093
  self,
1094
  merchant_id: str,
1095
- from_location_id: str,
1096
- to_location_id: str,
1097
  sku: str,
1098
  batch_no: str,
1099
  qty: Decimal,
@@ -1106,13 +1106,13 @@ class StockService:
1106
  # Get catalogue_id and uom from existing stock
1107
  stock_query = text("""
1108
  SELECT catalogue_id, uom FROM trans.scm_stock
1109
- WHERE merchant_id = :merchant_id AND location_id = :from_location_id
1110
  AND sku = :sku AND batch_no = :batch_no AND qty_available >= :qty
1111
  """)
1112
 
1113
  result = await self.db.execute(stock_query, {
1114
  "merchant_id": merchant_id,
1115
- "from_location_id": from_location_id,
1116
  "sku": sku,
1117
  "batch_no": batch_no,
1118
  "qty": float(qty)
@@ -1128,7 +1128,7 @@ class StockService:
1128
  # Create transfer OUT transaction
1129
  transfer_out = StockTransaction(
1130
  merchant_id=merchant_id,
1131
- location_id=from_location_id,
1132
  catalogue_id=catalogue_id,
1133
  sku=sku,
1134
  batch_no=batch_no,
@@ -1138,14 +1138,14 @@ class StockService:
1138
  txn_type=TransactionType.TRANSFER_OUT,
1139
  ref_type=ReferenceType.TRANSFER,
1140
  ref_id=ref_id,
1141
- remarks=f"Transfer to {to_location_id}: {remarks}" if remarks else f"Transfer to {to_location_id}",
1142
  created_by=created_by
1143
  )
1144
 
1145
  # Create transfer IN transaction
1146
  transfer_in = StockTransaction(
1147
  merchant_id=merchant_id,
1148
- location_id=to_location_id,
1149
  catalogue_id=catalogue_id,
1150
  sku=sku,
1151
  batch_no=batch_no,
@@ -1155,7 +1155,7 @@ class StockService:
1155
  txn_type=TransactionType.TRANSFER_IN,
1156
  ref_type=ReferenceType.TRANSFER,
1157
  ref_id=ref_id,
1158
- remarks=f"Transfer from {from_location_id}: {remarks}" if remarks else f"Transfer from {from_location_id}",
1159
  created_by=created_by
1160
  )
1161
 
@@ -1167,7 +1167,7 @@ class StockService:
1167
  success = all(result[1] for result in results)
1168
 
1169
  if success:
1170
- logger.info(f"Stock transfer completed: {sku} from {from_location_id} to {to_location_id}")
1171
 
1172
  return success
1173
 
 
43
  def __init__(
44
  self,
45
  merchant_id: str,
46
+ warehouse_id: str,
47
  catalogue_id: str,
48
  sku: str,
49
  batch_no: str,
 
58
  created_by: str = "system"
59
  ):
60
  self.merchant_id = merchant_id
61
+ self.warehouse_id = warehouse_id
62
  self.catalogue_id = catalogue_id
63
  self.sku = sku
64
  self.batch_no = batch_no
 
93
  # # Call stored procedure with correct parameters (10 parameters as per procedure definition)
94
  # query = text("""
95
  # SELECT trans.apply_stock_movement(
96
+ # :merchant_id, :warehouse_id, :sku, :batch_no, :qty,
97
  # :txn_type, :ref_type, :ref_id, :ref_no, :created_by
98
  # )
99
  # """)
100
 
101
  # await self.db.execute(query, {
102
  # "merchant_id": transaction.merchant_id,
103
+ # "warehouse_id": transaction.warehouse_id,
104
  # "sku": transaction.sku,
105
  # "batch_no": transaction.batch_no,
106
  # "qty": float(transaction.qty), # Convert Decimal to float for PostgreSQL NUMERIC
 
145
  for transaction in transactions:
146
  movement = {
147
  "merchant_id": transaction.merchant_id,
148
+ "warehouse_id": transaction.warehouse_id,
149
  "sku": transaction.sku,
150
  "batch_no": transaction.batch_no,
151
  "catalogue_id": transaction.catalogue_id,
 
161
 
162
  # Call bulk stored procedure
163
  query = text("SELECT trans.apply_bulk_stock_movements(:movements) as results")
164
+ logger.info(f"merchant_id: {transaction.merchant_id},warehouse_id: {transaction.warehouse_id},sku: {transaction.sku},batch_no: {transaction.batch_no},catalogue_id: {transaction.catalogue_id} ,uom: {transaction.uom}, expiry_date: {transaction.exp_dt} ,qty: {str(transaction.qty)},txn_type: {transaction.txn_type.value} ,ref_type: {transaction.ref_type.value} , ref_id: {str(transaction.ref_id)} ,created_by: {transaction.created_by}")
165
 
166
  result = await self.db.execute(query, {
167
  "movements": json.dumps(movements)
 
202
  if not transaction.merchant_id:
203
  raise ValueError("merchant_id is required")
204
 
205
+ if not transaction.warehouse_id:
206
+ raise ValueError("warehouse_id is required")
207
 
208
  if not transaction.sku:
209
  raise ValueError("sku is required")
 
255
  for item in grn_items:
256
  transaction = StockTransaction(
257
  merchant_id=item.merchant_id,
258
+ warehouse_id=item.wh_location or "default",
259
  catalogue_id=str(item.catalogue_id),
260
  sku=item.sku,
261
  batch_no=item.batch_no,
 
284
  async def process_stock_adjustment(
285
  self,
286
  merchant_id: str,
287
+ warehouse_id: str,
288
  sku: str,
289
  batch_no: str,
290
  adjustment_qty: Decimal,
 
305
  # Get catalogue_id and uom from existing stock
306
  stock_query = text("""
307
  SELECT catalogue_id, uom FROM trans.scm_stock
308
+ WHERE merchant_id = :merchant_id AND warehouse_id = :warehouse_id
309
  AND sku = :sku AND batch_no = :batch_no
310
  """)
311
 
312
  result = await self.db.execute(stock_query, {
313
  "merchant_id": merchant_id,
314
+ "warehouse_id": warehouse_id,
315
  "sku": sku,
316
  "batch_no": batch_no
317
  })
 
329
 
330
  transaction = StockTransaction(
331
  merchant_id=merchant_id,
332
+ warehouse_id=warehouse_id,
333
  catalogue_id=catalogue_id,
334
  sku=sku,
335
  batch_no=batch_no,
 
352
  async def get_stock_summary(
353
  self,
354
  merchant_id: str,
355
+ warehouse_id: Optional[str] = None,
356
  sku: Optional[str] = None
357
  ) -> List[Dict[str, Any]]:
358
  """Get current stock summary using stored procedure"""
359
  try:
360
+ query = text("SELECT * FROM get_stock_summary(:merchant_id, :warehouse_id, :sku)")
361
  result = await self.db.execute(query, {
362
  "merchant_id": merchant_id,
363
+ "warehouse_id": warehouse_id,
364
  "sku": sku
365
  })
366
 
 
369
  return [
370
  {
371
  "stock_id": str(stock.stock_id),
372
+ "warehouse_id": stock.warehouse_id,
373
  "catalogue_id": str(stock.catalogue_id),
374
  "sku": stock.sku,
375
  "batch_no": stock.batch_no,
 
408
  return [
409
  {
410
  "ledger_id": str(entry.ledger_id),
411
+ "warehouse_id": entry.warehouse_id,
412
  "catalogue_id": str(entry.catalogue_id),
413
  "sku": entry.sku,
414
  "batch_no": entry.batch_no,
 
441
  # Build base query
442
  base_query = """
443
  SELECT
444
+ stock_id, merchant_id, warehouse_id, catalogue_id, sku, batch_no,
445
  exp_dt, qty_on_hand, qty_reserved, qty_available, uom,
446
  created_at, updated_at, last_updated_at
447
  FROM trans.scm_stock
 
456
  if key == "merchant_id":
457
  where_conditions.append("merchant_id = :merchant_id")
458
  params["merchant_id"] = value
459
+ elif key == "warehouse_id":
460
+ where_conditions.append("warehouse_id = :warehouse_id")
461
+ params["warehouse_id"] = value
462
  elif key == "sku":
463
  where_conditions.append("sku ILIKE :sku")
464
  params["sku"] = f"%{value}%"
 
494
  row_dict = {
495
  "stock_id": str(row.stock_id),
496
  "merchant_id": row.merchant_id,
497
+ "warehouse_id": row.warehouse_id,
498
  "catalogue_id": str(row.catalogue_id),
499
  "sku": row.sku,
500
  "batch_no": row.batch_no,
 
541
  # Build base query
542
  base_query = """
543
  SELECT
544
+ ledger_id, merchant_id, warehouse_id, catalogue_id, sku, batch_no,
545
  exp_dt, qty, uom, txn_type, ref_type, ref_id, ref_no,
546
  remarks, created_by, created_at
547
  FROM trans.scm_stock_ledger
 
556
  if key == "merchant_id":
557
  where_conditions.append("merchant_id = :merchant_id")
558
  params["merchant_id"] = value
559
+ elif key == "warehouse_id":
560
+ where_conditions.append("warehouse_id = :warehouse_id")
561
+ params["warehouse_id"] = value
562
  elif key == "sku":
563
  where_conditions.append("sku ILIKE :sku")
564
  params["sku"] = f"%{value}%"
 
594
  row_dict = {
595
  "ledger_id": str(row.ledger_id),
596
  "merchant_id": row.merchant_id,
597
+ "warehouse_id": row.warehouse_id,
598
  "catalogue_id": str(row.catalogue_id),
599
  "sku": row.sku,
600
  "batch_no": row.batch_no,
 
643
  # Build base query
644
  base_query = """
645
  SELECT
646
+ adjustment_id, merchant_id, warehouse_id, catalogue_id, sku, batch_no,
647
  adjustment_qty, adjustment_type, reason, status, approved_by,
648
  approved_at, created_by, created_at, updated_at
649
  FROM trans.scm_stock_adjustment
 
658
  if key == "merchant_id":
659
  where_conditions.append("merchant_id = :merchant_id")
660
  params["merchant_id"] = value
661
+ elif key == "warehouse_id":
662
+ where_conditions.append("warehouse_id = :warehouse_id")
663
+ params["warehouse_id"] = value
664
  elif key == "sku":
665
  where_conditions.append("sku ILIKE :sku")
666
  params["sku"] = f"%{value}%"
 
693
  row_dict = {
694
  "adjustment_id": str(row.adjustment_id),
695
  "merchant_id": row.merchant_id,
696
+ "warehouse_id": row.warehouse_id,
697
  "catalogue_id": str(row.catalogue_id),
698
  "sku": row.sku,
699
  "batch_no": row.batch_no,
 
732
  async def reserve_stock(
733
  self,
734
  merchant_id: str,
735
+ warehouse_id: str,
736
  sku: str,
737
  batch_no: str,
738
  qty: Decimal,
 
742
  """Reserve stock using stored procedure"""
743
  try:
744
  query = text("""
745
+ SELECT reserve_stock(:merchant_id, :warehouse_id, :sku, :batch_no, :qty, :ref_id, :created_by)
746
  """)
747
 
748
  result = await self.db.execute(query, {
749
  "merchant_id": merchant_id,
750
+ "warehouse_id": warehouse_id,
751
  "sku": sku,
752
  "batch_no": batch_no,
753
  "qty": float(qty),
 
769
  async def release_stock_reservation(
770
  self,
771
  merchant_id: str,
772
+ warehouse_id: str,
773
  sku: str,
774
  batch_no: str,
775
  qty: Decimal,
 
779
  """Release stock reservation using stored procedure"""
780
  try:
781
  query = text("""
782
+ SELECT release_stock_reservation(:merchant_id, :warehouse_id, :sku, :batch_no, :qty, :ref_id, :created_by)
783
  """)
784
 
785
  result = await self.db.execute(query, {
786
  "merchant_id": merchant_id,
787
+ "warehouse_id": warehouse_id,
788
  "sku": sku,
789
  "batch_no": batch_no,
790
  "qty": float(qty),
 
806
  async def get_stock_summary_with_projection(
807
  self,
808
  merchant_id: str,
809
+ warehouse_id: Optional[str] = None,
810
  sku: Optional[str] = None,
811
  catalogue_id: Optional[str] = None,
812
  projection_list: Optional[List[str]] = None
 
814
  """Get stock summary with optional projection"""
815
  try:
816
  # Build query with optional filters
817
+ query_parts = ["SELECT * FROM get_stock_summary(:merchant_id, :warehouse_id, :sku)"]
818
  params = {
819
  "merchant_id": merchant_id,
820
+ "warehouse_id": warehouse_id,
821
  "sku": sku
822
  }
823
 
824
  # If catalogue_id is provided, add it as a filter
825
  if catalogue_id:
826
  query_parts[0] = """
827
+ SELECT s.* FROM get_stock_summary(:merchant_id, :warehouse_id, :sku) s
828
  WHERE s.catalogue_id = :catalogue_id
829
  """
830
  params["catalogue_id"] = catalogue_id
 
837
  for stock in stocks:
838
  stock_dict = {
839
  "stock_id": str(stock.stock_id),
840
+ "warehouse_id": stock.warehouse_id,
841
  "catalogue_id": str(stock.catalogue_id),
842
  "sku": stock.sku,
843
  "batch_no": stock.batch_no,
 
877
  for item in items:
878
  sku = item.get("sku")
879
  required_qty = item.get("qty", 0)
880
+ warehouse_id = item.get("warehouse_id")
881
 
882
  if not sku:
883
  continue
 
885
  # Query available stock
886
  query = text("""
887
  SELECT
888
+ sku, warehouse_id, batch_no, qty_available, exp_dt,
889
  catalogue_id, uom
890
  FROM trans.scm_stock
891
  WHERE merchant_id = :merchant_id
892
  AND sku = :sku
893
  AND qty_available > 0
894
+ AND (:warehouse_id IS NULL OR warehouse_id = :warehouse_id)
895
  ORDER BY exp_dt ASC NULLS LAST, created_at ASC
896
  """)
897
 
898
  result = await self.db.execute(query, {
899
  "merchant_id": merchant_id,
900
  "sku": sku,
901
+ "warehouse_id": warehouse_id
902
  })
903
 
904
  stock_rows = result.fetchall()
 
912
  "required_qty": required_qty,
913
  "total_available": total_available,
914
  "is_available": is_available,
915
+ "warehouse_id": warehouse_id,
916
  "batches": [
917
  {
918
  "batch_no": row.batch_no,
 
920
  "exp_dt": row.exp_dt.isoformat() if row.exp_dt else None,
921
  "catalogue_id": str(row.catalogue_id),
922
  "uom": row.uom,
923
+ "warehouse_id": row.warehouse_id
924
  }
925
  for row in stock_rows
926
  ]
 
945
  async def get_stock_valuation(
946
  self,
947
  merchant_id: str,
948
+ warehouse_id: Optional[str] = None,
949
  as_of_date: Optional[datetime] = None,
950
  projection_list: Optional[List[str]] = None
951
  ) -> List[Dict[str, Any]]:
 
954
  # Build query for stock valuation
955
  query = text("""
956
  SELECT
957
+ s.stock_id, s.warehouse_id, s.catalogue_id, s.sku, s.batch_no,
958
  s.qty_on_hand, s.uom, s.exp_dt,
959
  cr.mrp, cr.base_price,
960
  (s.qty_on_hand * COALESCE(cr.base_price, cr.mrp, 0)) as valuation_amount
 
962
  LEFT JOIN trans.catalogue_ref cr ON s.catalogue_id = cr.catalogue_id
963
  WHERE s.merchant_id = :merchant_id
964
  AND s.qty_on_hand > 0
965
+ AND (:warehouse_id IS NULL OR s.warehouse_id = :warehouse_id)
966
  AND (:as_of_date IS NULL OR s.created_at <= :as_of_date)
967
  ORDER BY s.sku, s.batch_no
968
  """)
969
 
970
  params = {
971
  "merchant_id": merchant_id,
972
+ "warehouse_id": warehouse_id,
973
  "as_of_date": as_of_date
974
  }
975
 
 
986
 
987
  valuation_dict = {
988
  "stock_id": str(row.stock_id),
989
+ "warehouse_id": row.warehouse_id,
990
  "catalogue_id": str(row.catalogue_id),
991
  "sku": row.sku,
992
  "batch_no": row.batch_no,
 
1032
  try:
1033
  query = text("""
1034
  SELECT
1035
+ s.stock_id, s.merchant_id, s.warehouse_id, s.catalogue_id, s.sku, s.batch_no,
1036
  s.exp_dt, s.qty_on_hand, s.qty_reserved, s.qty_available, s.uom,
1037
  s.created_at, s.updated_at, s.last_updated_at,
1038
  cr.catalogue_name, cr.mrp, cr.base_price
 
1053
  stock_dict = {
1054
  "stock_id": str(row.stock_id),
1055
  "merchant_id": row.merchant_id,
1056
+ "warehouse_id": row.warehouse_id,
1057
  "catalogue_id": str(row.catalogue_id),
1058
  "sku": row.sku,
1059
  "batch_no": row.batch_no,
 
1092
  async def process_stock_transfer(
1093
  self,
1094
  merchant_id: str,
1095
+ from_warehouse_id: str,
1096
+ to_warehouse_id: str,
1097
  sku: str,
1098
  batch_no: str,
1099
  qty: Decimal,
 
1106
  # Get catalogue_id and uom from existing stock
1107
  stock_query = text("""
1108
  SELECT catalogue_id, uom FROM trans.scm_stock
1109
+ WHERE merchant_id = :merchant_id AND warehouse_id = :from_warehouse_id
1110
  AND sku = :sku AND batch_no = :batch_no AND qty_available >= :qty
1111
  """)
1112
 
1113
  result = await self.db.execute(stock_query, {
1114
  "merchant_id": merchant_id,
1115
+ "from_warehouse_id": from_warehouse_id,
1116
  "sku": sku,
1117
  "batch_no": batch_no,
1118
  "qty": float(qty)
 
1128
  # Create transfer OUT transaction
1129
  transfer_out = StockTransaction(
1130
  merchant_id=merchant_id,
1131
+ warehouse_id=from_warehouse_id,
1132
  catalogue_id=catalogue_id,
1133
  sku=sku,
1134
  batch_no=batch_no,
 
1138
  txn_type=TransactionType.TRANSFER_OUT,
1139
  ref_type=ReferenceType.TRANSFER,
1140
  ref_id=ref_id,
1141
+ remarks=f"Transfer to {to_warehouse_id}: {remarks}" if remarks else f"Transfer to {to_warehouse_id}",
1142
  created_by=created_by
1143
  )
1144
 
1145
  # Create transfer IN transaction
1146
  transfer_in = StockTransaction(
1147
  merchant_id=merchant_id,
1148
+ warehouse_id=to_warehouse_id,
1149
  catalogue_id=catalogue_id,
1150
  sku=sku,
1151
  batch_no=batch_no,
 
1155
  txn_type=TransactionType.TRANSFER_IN,
1156
  ref_type=ReferenceType.TRANSFER,
1157
  ref_id=ref_id,
1158
+ remarks=f"Transfer from {from_warehouse_id}: {remarks}" if remarks else f"Transfer from {from_warehouse_id}",
1159
  created_by=created_by
1160
  )
1161
 
 
1167
  success = all(result[1] for result in results)
1168
 
1169
  if success:
1170
+ logger.info(f"Stock transfer completed: {sku} from {from_warehouse_id} to {to_warehouse_id}")
1171
 
1172
  return success
1173
 
app/inventory/stock_take/controllers/router.py CHANGED
@@ -140,7 +140,7 @@ async def get_stock_take(
140
  {
141
  "stock_take_id": "detail-uuid",
142
  "merchant_id": "merchant_123",
143
- "location_id": "WH_001",
144
  "sku": "SKU_001",
145
  "system_qty": 100,
146
  "physical_qty": 98,
@@ -182,7 +182,7 @@ async def get_stock_take_session(
182
  "stock_take_master_id": "uuid",
183
  "stock_take_number": "ST-2024-12-001",
184
  "merchant_id": "merchant_123",
185
- "location_id": "WH_001",
186
  "status": "completed",
187
  "total_items": 3,
188
  "total_variance_value": -45.50,
@@ -220,7 +220,7 @@ async def list_stock_takes(
220
  **Request Body:**
221
  - **filters**: Filter criteria (optional)
222
  - merchant_id: Filter by merchant
223
- - location_id: Filter by location
224
  - status: Filter by status (draft, completed, applied)
225
  - stock_take_number: Search by stock take number (partial match)
226
  - **skip**: Number of records to skip (pagination)
@@ -229,7 +229,7 @@ async def list_stock_takes(
229
 
230
  **Projection Examples:**
231
  - Basic info: ["stock_take_master_id", "stock_take_number", "status", "total_items"]
232
- - Summary: ["stock_take_number", "location_id", "total_items", "total_variance_value", "status"]
233
  - Full details: null (returns all fields)
234
 
235
  **Filter Examples:**
@@ -439,21 +439,21 @@ async def list_pending_approvals(
439
 
440
  **Request Body:**
441
  - **filters**: Additional filter criteria (optional)
442
- - location_id: Filter by location
443
  - created_by: Filter by creator
444
  - **skip**: Number of records to skip (pagination)
445
  - **limit**: Maximum records to return (1-1000)
446
  - **projection_list**: List of fields to include in response (optional)
447
 
448
  **Useful Projections:**
449
- - Dashboard: ["stock_take_number", "location_id", "total_items", "total_variance_value", "created_by", "created_at"]
450
  - Summary: ["stock_take_master_id", "stock_take_number", "status", "total_variance_value"]
451
 
452
  **Example:**
453
  ```json
454
  {
455
  "filters": {
456
- "location_id": "WH_001"
457
  },
458
  "skip": 0,
459
  "limit": 50,
 
140
  {
141
  "stock_take_id": "detail-uuid",
142
  "merchant_id": "merchant_123",
143
+ "warehouse_id": "WH_001",
144
  "sku": "SKU_001",
145
  "system_qty": 100,
146
  "physical_qty": 98,
 
182
  "stock_take_master_id": "uuid",
183
  "stock_take_number": "ST-2024-12-001",
184
  "merchant_id": "merchant_123",
185
+ "warehouse_id": "WH_001",
186
  "status": "completed",
187
  "total_items": 3,
188
  "total_variance_value": -45.50,
 
220
  **Request Body:**
221
  - **filters**: Filter criteria (optional)
222
  - merchant_id: Filter by merchant
223
+ - warehouse_id: Filter by location
224
  - status: Filter by status (draft, completed, applied)
225
  - stock_take_number: Search by stock take number (partial match)
226
  - **skip**: Number of records to skip (pagination)
 
229
 
230
  **Projection Examples:**
231
  - Basic info: ["stock_take_master_id", "stock_take_number", "status", "total_items"]
232
+ - Summary: ["stock_take_number", "warehouse_id", "total_items", "total_variance_value", "status"]
233
  - Full details: null (returns all fields)
234
 
235
  **Filter Examples:**
 
439
 
440
  **Request Body:**
441
  - **filters**: Additional filter criteria (optional)
442
+ - warehouse_id: Filter by location
443
  - created_by: Filter by creator
444
  - **skip**: Number of records to skip (pagination)
445
  - **limit**: Maximum records to return (1-1000)
446
  - **projection_list**: List of fields to include in response (optional)
447
 
448
  **Useful Projections:**
449
+ - Dashboard: ["stock_take_number", "warehouse_id", "total_items", "total_variance_value", "created_by", "created_at"]
450
  - Summary: ["stock_take_master_id", "stock_take_number", "status", "total_variance_value"]
451
 
452
  **Example:**
453
  ```json
454
  {
455
  "filters": {
456
+ "warehouse_id": "WH_001"
457
  },
458
  "skip": 0,
459
  "limit": 50,
app/inventory/stock_take/schemas/approval_schema.py CHANGED
@@ -38,7 +38,7 @@ class StockTakeMasterRead(BaseModel):
38
  stock_take_master_id: UUID
39
  stock_take_number: str
40
  merchant_id: str
41
- location_id: str
42
  stock_take_date: datetime
43
  description: Optional[str]
44
  additional_notes: Optional[str]
@@ -100,7 +100,7 @@ class StockTakeApprovalListRequest(BaseModel):
100
  "example": {
101
  "filters": {
102
  "status": "submitted",
103
- "location_id": "WH_001"
104
  },
105
  "skip": 0,
106
  "limit": 50,
@@ -133,7 +133,7 @@ class StockTakeSummaryRead(BaseModel):
133
  """Summary view for stock take approval dashboard"""
134
  stock_take_master_id: UUID
135
  stock_take_number: str
136
- location_id: str
137
  total_items: int
138
  total_variance_value: float
139
  status: str
 
38
  stock_take_master_id: UUID
39
  stock_take_number: str
40
  merchant_id: str
41
+ warehouse_id: str
42
  stock_take_date: datetime
43
  description: Optional[str]
44
  additional_notes: Optional[str]
 
100
  "example": {
101
  "filters": {
102
  "status": "submitted",
103
+ "warehouse_id": "WH_001"
104
  },
105
  "skip": 0,
106
  "limit": 50,
 
133
  """Summary view for stock take approval dashboard"""
134
  stock_take_master_id: UUID
135
  stock_take_number: str
136
+ warehouse_id: str
137
  total_items: int
138
  total_variance_value: float
139
  status: str
app/inventory/stock_take/schemas/schema.py CHANGED
@@ -50,7 +50,7 @@ class StockTakeSessionResponse(BaseModel):
50
  stock_take_master_id: str
51
  stock_take_number: str
52
  merchant_id: str
53
- location_id: str
54
  stock_take_date: datetime
55
  description: Optional[str]
56
  additional_notes: Optional[str]
@@ -71,7 +71,7 @@ class StockTakeMasterResponse(BaseModel):
71
  stock_take_master_id: str
72
  stock_take_number: str
73
  merchant_id: str
74
- location_id: str
75
  stock_take_date: datetime
76
  description: Optional[str]
77
  additional_notes: Optional[str]
@@ -88,7 +88,7 @@ class StockTakeResponse(BaseModel):
88
  """Response schema for stock take"""
89
  stock_take_id: str
90
  merchant_id: str
91
- location_id: str
92
  sku: str
93
  batch_no: Optional[str]
94
  system_qty: int
 
50
  stock_take_master_id: str
51
  stock_take_number: str
52
  merchant_id: str
53
+ warehouse_id: str
54
  stock_take_date: datetime
55
  description: Optional[str]
56
  additional_notes: Optional[str]
 
71
  stock_take_master_id: str
72
  stock_take_number: str
73
  merchant_id: str
74
+ warehouse_id: str
75
  stock_take_date: datetime
76
  description: Optional[str]
77
  additional_notes: Optional[str]
 
88
  """Response schema for stock take"""
89
  stock_take_id: str
90
  merchant_id: str
91
+ warehouse_id: str
92
  sku: str
93
  batch_no: Optional[str]
94
  system_qty: int
app/inventory/stock_take/services/approval_service.py CHANGED
@@ -210,7 +210,7 @@ class StockTakeApprovalService:
210
  stock_query = select(ScmStock).where(
211
  and_(
212
  ScmStock.merchant_id == master.merchant_id,
213
- ScmStock.location_id == master.location_id,
214
  ScmStock.sku == detail.sku
215
  )
216
  )
@@ -236,7 +236,7 @@ class StockTakeApprovalService:
236
  stock_transactions.append(
237
  StockTransaction(
238
  merchant_id=master.merchant_id,
239
- location_id=master.location_id,
240
  catalogue_id=str(catalogue_id),
241
  sku=detail.sku,
242
  batch_no=detail.batch_no,
@@ -478,8 +478,8 @@ class StockTakeApprovalService:
478
  elif filters.get("merchant_id"):
479
  conditions.append(ScmStockTakeMaster.merchant_id == filters["merchant_id"])
480
 
481
- if filters.get("location_id"):
482
- conditions.append(ScmStockTakeMaster.location_id == filters["location_id"])
483
 
484
  if filters.get("created_by"):
485
  conditions.append(ScmStockTakeMaster.created_by == filters["created_by"])
 
210
  stock_query = select(ScmStock).where(
211
  and_(
212
  ScmStock.merchant_id == master.merchant_id,
213
+ ScmStock.warehouse_id == master.warehouse_id,
214
  ScmStock.sku == detail.sku
215
  )
216
  )
 
236
  stock_transactions.append(
237
  StockTransaction(
238
  merchant_id=master.merchant_id,
239
+ warehouse_id=master.warehouse_id,
240
  catalogue_id=str(catalogue_id),
241
  sku=detail.sku,
242
  batch_no=detail.batch_no,
 
478
  elif filters.get("merchant_id"):
479
  conditions.append(ScmStockTakeMaster.merchant_id == filters["merchant_id"])
480
 
481
+ if filters.get("warehouse_id"):
482
+ conditions.append(ScmStockTakeMaster.warehouse_id == filters["warehouse_id"])
483
 
484
  if filters.get("created_by"):
485
  conditions.append(ScmStockTakeMaster.created_by == filters["created_by"])
app/inventory/stock_take/services/service.py CHANGED
@@ -36,7 +36,7 @@ class StockTakeService:
36
  @staticmethod
37
  async def _validate_stock_record(
38
  merchant_id: str,
39
- location_id: str,
40
  sku: str,
41
  batch_no: Optional[str]
42
  ) -> bool:
@@ -46,7 +46,7 @@ class StockTakeService:
46
  query = select(ScmStock).where(
47
  and_(
48
  ScmStock.merchant_id == merchant_id,
49
- ScmStock.location_id == location_id,
50
  ScmStock.sku == sku
51
  )
52
  )
@@ -100,7 +100,7 @@ class StockTakeService:
100
  master = ScmStockTakeMaster(
101
  stock_take_number=stock_take_number,
102
  merchant_id=effective_merchant_id,
103
- location_id=payload.warehouse_id,
104
  stock_take_date=payload.stock_take_date or datetime.utcnow(),
105
  description=f"Stock take session - {len(payload.entries)} items",
106
  additional_notes=payload.additional_notes,
@@ -121,7 +121,7 @@ class StockTakeService:
121
  stock_query = select(ScmStock).where(
122
  and_(
123
  ScmStock.merchant_id == effective_merchant_id,
124
- ScmStock.location_id == payload.warehouse_id,
125
  ScmStock.sku == entry.sku
126
  )
127
  )
@@ -168,7 +168,7 @@ class StockTakeService:
168
  results.append(StockTakeResponse(
169
  stock_take_id=str(detail.stock_take_detail_id),
170
  merchant_id=master.merchant_id,
171
- location_id=master.location_id,
172
  sku=detail.sku,
173
  batch_no=detail.batch_no,
174
  system_qty=int(detail.system_qty),
@@ -269,7 +269,7 @@ class StockTakeService:
269
  # Create internal stock adjustment for the variance
270
  adjustment_request = CreateStockAdjustmentRequestSingle(
271
  merchant_id=master.merchant_id,
272
- location_id=master.location_id,
273
  sku=detail.sku,
274
  batch_no=detail.batch_no,
275
  adj_type=AdjustmentType.CYCLE_COUNT,
@@ -320,7 +320,7 @@ class StockTakeService:
320
  return StockTakeResponse(
321
  stock_take_id=str(detail.stock_take_detail_id),
322
  merchant_id=master.merchant_id,
323
- location_id=master.location_id,
324
  sku=detail.sku,
325
  batch_no=detail.batch_no,
326
  system_qty=int(detail.system_qty),
@@ -369,7 +369,7 @@ class StockTakeService:
369
  return StockTakeResponse(
370
  stock_take_id=str(detail.stock_take_detail_id),
371
  merchant_id=master.merchant_id,
372
- location_id=master.location_id,
373
  sku=detail.sku,
374
  batch_no=detail.batch_no,
375
  system_qty=int(detail.system_qty),
@@ -415,7 +415,7 @@ class StockTakeService:
415
  return StockTakeResponse(
416
  stock_take_id=str(first_detail.stock_take_detail_id),
417
  merchant_id=master.merchant_id,
418
- location_id=master.location_id,
419
  sku=first_detail.sku,
420
  batch_no=first_detail.batch_no,
421
  system_qty=int(first_detail.system_qty),
@@ -510,7 +510,7 @@ class StockTakeService:
510
  stock_take_master_id=str(master.stock_take_master_id),
511
  stock_take_number=master.stock_take_number,
512
  merchant_id=master.merchant_id,
513
- location_id=master.location_id,
514
  stock_take_date=master.stock_take_date,
515
  description=master.description,
516
  additional_notes=master.additional_notes,
@@ -556,8 +556,8 @@ class StockTakeService:
556
  elif filters.get("merchant_id"):
557
  conditions.append(ScmStockTakeMaster.merchant_id == filters["merchant_id"])
558
 
559
- if filters.get("location_id"):
560
- conditions.append(ScmStockTakeMaster.location_id == filters["location_id"])
561
  if filters.get("status"):
562
  conditions.append(ScmStockTakeMaster.status == filters["status"])
563
  if filters.get("stock_take_number"):
@@ -600,7 +600,7 @@ class StockTakeService:
600
  stock_take_master_id=str(master.stock_take_master_id),
601
  stock_take_number=master.stock_take_number,
602
  merchant_id=master.merchant_id,
603
- location_id=master.location_id,
604
  stock_take_date=master.stock_take_date,
605
  description=master.description,
606
  additional_notes=master.additional_notes,
 
36
  @staticmethod
37
  async def _validate_stock_record(
38
  merchant_id: str,
39
+ warehouse_id: str,
40
  sku: str,
41
  batch_no: Optional[str]
42
  ) -> bool:
 
46
  query = select(ScmStock).where(
47
  and_(
48
  ScmStock.merchant_id == merchant_id,
49
+ ScmStock.warehouse_id == warehouse_id,
50
  ScmStock.sku == sku
51
  )
52
  )
 
100
  master = ScmStockTakeMaster(
101
  stock_take_number=stock_take_number,
102
  merchant_id=effective_merchant_id,
103
+ warehouse_id=payload.warehouse_id,
104
  stock_take_date=payload.stock_take_date or datetime.utcnow(),
105
  description=f"Stock take session - {len(payload.entries)} items",
106
  additional_notes=payload.additional_notes,
 
121
  stock_query = select(ScmStock).where(
122
  and_(
123
  ScmStock.merchant_id == effective_merchant_id,
124
+ ScmStock.warehouse_id == payload.warehouse_id,
125
  ScmStock.sku == entry.sku
126
  )
127
  )
 
168
  results.append(StockTakeResponse(
169
  stock_take_id=str(detail.stock_take_detail_id),
170
  merchant_id=master.merchant_id,
171
+ warehouse_id=master.warehouse_id,
172
  sku=detail.sku,
173
  batch_no=detail.batch_no,
174
  system_qty=int(detail.system_qty),
 
269
  # Create internal stock adjustment for the variance
270
  adjustment_request = CreateStockAdjustmentRequestSingle(
271
  merchant_id=master.merchant_id,
272
+ warehouse_id=master.warehouse_id,
273
  sku=detail.sku,
274
  batch_no=detail.batch_no,
275
  adj_type=AdjustmentType.CYCLE_COUNT,
 
320
  return StockTakeResponse(
321
  stock_take_id=str(detail.stock_take_detail_id),
322
  merchant_id=master.merchant_id,
323
+ warehouse_id=master.warehouse_id,
324
  sku=detail.sku,
325
  batch_no=detail.batch_no,
326
  system_qty=int(detail.system_qty),
 
369
  return StockTakeResponse(
370
  stock_take_id=str(detail.stock_take_detail_id),
371
  merchant_id=master.merchant_id,
372
+ warehouse_id=master.warehouse_id,
373
  sku=detail.sku,
374
  batch_no=detail.batch_no,
375
  system_qty=int(detail.system_qty),
 
415
  return StockTakeResponse(
416
  stock_take_id=str(first_detail.stock_take_detail_id),
417
  merchant_id=master.merchant_id,
418
+ warehouse_id=master.warehouse_id,
419
  sku=first_detail.sku,
420
  batch_no=first_detail.batch_no,
421
  system_qty=int(first_detail.system_qty),
 
510
  stock_take_master_id=str(master.stock_take_master_id),
511
  stock_take_number=master.stock_take_number,
512
  merchant_id=master.merchant_id,
513
+ warehouse_id=master.warehouse_id,
514
  stock_take_date=master.stock_take_date,
515
  description=master.description,
516
  additional_notes=master.additional_notes,
 
556
  elif filters.get("merchant_id"):
557
  conditions.append(ScmStockTakeMaster.merchant_id == filters["merchant_id"])
558
 
559
+ if filters.get("warehouse_id"):
560
+ conditions.append(ScmStockTakeMaster.warehouse_id == filters["warehouse_id"])
561
  if filters.get("status"):
562
  conditions.append(ScmStockTakeMaster.status == filters["status"])
563
  if filters.get("stock_take_number"):
 
600
  stock_take_master_id=str(master.stock_take_master_id),
601
  stock_take_number=master.stock_take_number,
602
  merchant_id=master.merchant_id,
603
+ warehouse_id=master.warehouse_id,
604
  stock_take_date=master.stock_take_date,
605
  description=master.description,
606
  additional_notes=master.additional_notes,
app/purchases/orders/services/service.py CHANGED
@@ -210,7 +210,7 @@ class OrdersService:
210
  poi.tax_amt,
211
  poi.created_at,
212
  s.batch_no,
213
- s.location_id as warehouse_id,
214
  cr.catalogue_name
215
  FROM trans.scm_po_item poi
216
  LEFT JOIN trans.catalogue_ref cr ON poi.catalogue_id = cr.catalogue_id
 
210
  poi.tax_amt,
211
  poi.created_at,
212
  s.batch_no,
213
+ s.warehouse_id as warehouse_id,
214
  cr.catalogue_name
215
  FROM trans.scm_po_item poi
216
  LEFT JOIN trans.catalogue_ref cr ON poi.catalogue_id = cr.catalogue_id
app/sql/apply_bulk_stock_movements.sql CHANGED
@@ -23,7 +23,7 @@ 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,
 
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->>'warehouse_id')::TEXT,
27
  (v_movement->>'sku')::TEXT,
28
  (v_movement->>'batch_no')::TEXT,
29
  (v_movement->>'catalogue_id')::TEXT,
app/sql/apply_stock_movement.sql CHANGED
@@ -4,7 +4,7 @@
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,
@@ -67,7 +67,7 @@ BEGIN
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,
@@ -83,7 +83,7 @@ BEGIN
83
  INSERT INTO trans.scm_stock_ledger (
84
  ledger_id,
85
  merchant_id,
86
- location_id,
87
  sku,
88
  batch_no,
89
  txn_type,
@@ -95,7 +95,7 @@ BEGIN
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,
@@ -113,7 +113,7 @@ BEGIN
113
  ----------------------------------------------------------------
114
  INSERT INTO trans.scm_stock (
115
  merchant_id,
116
- location_id,
117
  sku,
118
  batch_no,
119
  catalogue_id,
@@ -128,7 +128,7 @@ BEGIN
128
  updated_at
129
  ) VALUES (
130
  p_merchant_id,
131
- p_location_id,
132
  p_sku,
133
  p_batch_no,
134
  p_catalogue_id,
@@ -142,7 +142,7 @@ BEGIN
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),
 
4
 
5
  CREATE OR REPLACE FUNCTION trans.apply_stock_movement(
6
  p_merchant_id text,
7
+ p_warehouse_id text,
8
  p_sku text,
9
  p_batch_no text,
10
  p_catalogue_id text,
 
67
  RAISE NOTICE 'Inserting ledger: %, %, %, %, %, %, %, %, %, %, %',
68
  v_ledger_id,
69
  p_merchant_id,
70
+ p_warehouse_id,
71
  p_sku,
72
  p_batch_no,
73
  p_txn_type,
 
83
  INSERT INTO trans.scm_stock_ledger (
84
  ledger_id,
85
  merchant_id,
86
+ warehouse_id,
87
  sku,
88
  batch_no,
89
  txn_type,
 
95
  ) VALUES (
96
  v_ledger_id,
97
  p_merchant_id,
98
+ p_warehouse_id,
99
  p_sku,
100
  p_batch_no,
101
  p_txn_type,
 
113
  ----------------------------------------------------------------
114
  INSERT INTO trans.scm_stock (
115
  merchant_id,
116
+ warehouse_id,
117
  sku,
118
  batch_no,
119
  catalogue_id,
 
128
  updated_at
129
  ) VALUES (
130
  p_merchant_id,
131
+ p_warehouse_id,
132
  p_sku,
133
  p_batch_no,
134
  p_catalogue_id,
 
142
  NOW(),
143
  NOW()
144
  )
145
+ ON CONFLICT (merchant_id, warehouse_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),
app/sql/fn_get_po_items_for_order_process.sql CHANGED
@@ -34,7 +34,7 @@ BEGIN
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
 
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.warehouse_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
app/sql/fn_get_po_items_for_purchase_return.sql CHANGED
@@ -37,7 +37,7 @@ BEGIN
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
 
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.warehouse_id = p_warehouse_id
41
 
42
  WHERE pi.po_id = p_po_id
43
  AND st.qty_available > 0
app/sql/get_stock_ledger.sql CHANGED
@@ -7,7 +7,7 @@ CREATE OR REPLACE FUNCTION trans.get_stock_ledger(
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
@@ -16,7 +16,7 @@ CREATE OR REPLACE FUNCTION trans.get_stock_ledger(
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
 
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, 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)
11
  LANGUAGE 'plpgsql'
12
  COST 100
13
  VOLATILE PARALLEL UNSAFE
 
16
  AS $BODY$
17
  BEGIN
18
  RETURN QUERY
19
+ SELECT l.ledger_id, l.warehouse_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
app/sql/get_stock_summary.sql CHANGED
@@ -4,9 +4,9 @@
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
@@ -15,11 +15,11 @@ CREATE OR REPLACE FUNCTION trans.get_stock_summary(
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;
 
4
 
5
  CREATE OR REPLACE FUNCTION trans.get_stock_summary(
6
  p_merchant_id character varying,
7
+ p_warehouse_id character varying DEFAULT NULL::character varying,
8
  p_sku character varying DEFAULT NULL::character varying)
9
+ 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, last_updated_at timestamp without time zone)
10
  LANGUAGE 'plpgsql'
11
  COST 100
12
  VOLATILE PARALLEL UNSAFE
 
15
  AS $BODY$
16
  BEGIN
17
  RETURN QUERY
18
+ SELECT s.stock_id, s.warehouse_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_warehouse_id IS NULL OR s.warehouse_id = p_warehouse_id)
23
  AND (p_sku IS NULL OR s.sku = p_sku)
24
  ORDER BY s.sku, s.batch_no;
25
  END;
app/sql/release_stock_reservation.sql CHANGED
@@ -4,7 +4,7 @@
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,
@@ -22,7 +22,7 @@ BEGIN
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
 
 
4
 
5
  CREATE OR REPLACE FUNCTION trans.release_stock_reservation(
6
  p_merchant_id character varying,
7
+ p_warehouse_id character varying,
8
  p_sku character varying,
9
  p_batch_no character varying,
10
  p_qty numeric,
 
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 warehouse_id = p_warehouse_id
26
  AND sku = p_sku
27
  AND batch_no = p_batch_no;
28
 
app/sql/reserve_stock.sql CHANGED
@@ -4,7 +4,7 @@
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,
@@ -23,7 +23,7 @@ BEGIN
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
 
@@ -39,7 +39,7 @@ BEGIN
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
 
 
4
 
5
  CREATE OR REPLACE FUNCTION trans.reserve_stock(
6
  p_merchant_id character varying,
7
+ p_warehouse_id character varying,
8
  p_sku character varying,
9
  p_batch_no character varying,
10
  p_qty numeric,
 
23
  INTO v_available_qty
24
  FROM trans.scm_stock
25
  WHERE merchant_id = p_merchant_id
26
+ AND warehouse_id = p_warehouse_id
27
  AND sku = p_sku
28
  AND batch_no = p_batch_no;
29
 
 
39
  qty_available = qty_available - p_qty,
40
  last_updated_at = NOW()
41
  WHERE merchant_id = p_merchant_id
42
+ AND warehouse_id = p_warehouse_id
43
  AND sku = p_sku
44
  AND batch_no = p_batch_no;
45
 
app/trade_sales/services/service.py CHANGED
@@ -492,7 +492,7 @@ class TradeSalesService:
492
  # Prepare stock transaction (outbound from supplier)
493
  stock_transaction = StockTransaction(
494
  merchant_id=supplier_id,
495
- location_id=str(request.warehouse_id),
496
  catalogue_id=str(order_item.catalogue_id),
497
  sku=ship_item.sku,
498
  batch_no=ship_item.batch_no,
 
492
  # Prepare stock transaction (outbound from supplier)
493
  stock_transaction = StockTransaction(
494
  merchant_id=supplier_id,
495
+ warehouse_id=str(request.warehouse_id),
496
  catalogue_id=str(order_item.catalogue_id),
497
  sku=ship_item.sku,
498
  batch_no=ship_item.batch_no,
app/transports/controllers/router.py CHANGED
@@ -54,7 +54,7 @@ async def create_transport(
54
  - tracking_url: Tracking URL template with {awb} placeholder
55
  - supports_cod: Cash on Delivery support (default: false)
56
  - supports_tracking: Tracking support (default: false)
57
- - location_ids: Location IDs (required if location_scope is 'selected')
58
  - serviceable_regions: List of serviceable regions
59
  - notes: Additional notes
60
 
@@ -193,7 +193,7 @@ async def list_transports(
193
  **Filters:**
194
  - type: Filter by transport type (courier, internal, freight)
195
  - status: Filter by status (active, inactive, draft)
196
- - location_id: Filter by location ID (shows transports with scope 'all' or containing this location)
197
  - search: Search in transport name or code
198
 
199
  **Pagination:**
@@ -216,7 +216,7 @@ async def list_transports(
216
  merchant_id=current_user.merchant_id, # Use merchant_id from JWT token
217
  transport_type=payload.type,
218
  status=payload.status,
219
- location_id=payload.location_id,
220
  search=payload.search,
221
  skip=payload.skip,
222
  limit=payload.limit,
 
54
  - tracking_url: Tracking URL template with {awb} placeholder
55
  - supports_cod: Cash on Delivery support (default: false)
56
  - supports_tracking: Tracking support (default: false)
57
+ - warehouse_ids: Location IDs (required if location_scope is 'selected')
58
  - serviceable_regions: List of serviceable regions
59
  - notes: Additional notes
60
 
 
193
  **Filters:**
194
  - type: Filter by transport type (courier, internal, freight)
195
  - status: Filter by status (active, inactive, draft)
196
+ - warehouse_id: Filter by location ID (shows transports with scope 'all' or containing this location)
197
  - search: Search in transport name or code
198
 
199
  **Pagination:**
 
216
  merchant_id=current_user.merchant_id, # Use merchant_id from JWT token
217
  transport_type=payload.type,
218
  status=payload.status,
219
+ warehouse_id=payload.warehouse_id,
220
  search=payload.search,
221
  skip=payload.skip,
222
  limit=payload.limit,
app/transports/models/model.py CHANGED
@@ -28,7 +28,7 @@ class TransportModel(BaseModel):
28
 
29
  # Coverage
30
  location_scope: str = Field(..., description="Location scope (all, selected)")
31
- location_ids: List[str] = Field(default_factory=list, description="Location IDs if scope is 'selected'")
32
  serviceable_regions: List[str] = Field(default_factory=list, description="Serviceable regions")
33
 
34
  # Status and Audit
@@ -56,7 +56,7 @@ class TransportModel(BaseModel):
56
  "supports_cod": True,
57
  "supports_tracking": True,
58
  "location_scope": "all",
59
- "location_ids": [],
60
  "serviceable_regions": ["Mumbai", "Delhi"],
61
  "status": "active",
62
  "notes": "Primary courier partner",
 
28
 
29
  # Coverage
30
  location_scope: str = Field(..., description="Location scope (all, selected)")
31
+ warehouse_ids: List[str] = Field(default_factory=list, description="Location IDs if scope is 'selected'")
32
  serviceable_regions: List[str] = Field(default_factory=list, description="Serviceable regions")
33
 
34
  # Status and Audit
 
56
  "supports_cod": True,
57
  "supports_tracking": True,
58
  "location_scope": "all",
59
+ "warehouse_ids": [],
60
  "serviceable_regions": ["Mumbai", "Delhi"],
61
  "status": "active",
62
  "notes": "Primary courier partner",
app/transports/schemas/schema.py CHANGED
@@ -24,7 +24,7 @@ class TransportCreate(BaseModel):
24
 
25
  # Coverage
26
  location_scope: str = Field(..., description="Location scope (all, selected)")
27
- location_ids: Optional[List[str]] = Field(None, description="Location IDs if scope is 'selected'")
28
  serviceable_regions: Optional[List[str]] = Field(None, description="Serviceable regions")
29
 
30
  notes: Optional[str] = Field(None, description="Additional notes")
@@ -108,7 +108,7 @@ class TransportCreate(BaseModel):
108
  "supports_cod": True,
109
  "supports_tracking": True,
110
  "location_scope": "all",
111
- "location_ids": None,
112
  "serviceable_regions": ["Mumbai", "Delhi"],
113
  "notes": "Primary courier partner"
114
  }
@@ -130,7 +130,7 @@ class TransportUpdate(BaseModel):
130
  supports_tracking: Optional[bool] = Field(None, description="Tracking support")
131
 
132
  location_scope: Optional[str] = Field(None, description="Location scope")
133
- location_ids: Optional[List[str]] = Field(None, description="Location IDs")
134
  serviceable_regions: Optional[List[str]] = Field(None, description="Serviceable regions")
135
 
136
  notes: Optional[str] = Field(None, description="Additional notes")
@@ -214,7 +214,7 @@ class TransportResponse(BaseModel):
214
  supports_tracking: bool = False
215
 
216
  location_scope: str
217
- location_ids: List[str] = Field(default_factory=list)
218
  serviceable_regions: List[str] = Field(default_factory=list)
219
 
220
  status: str
@@ -229,7 +229,7 @@ class TransportListRequest(BaseModel):
229
  """Schema for listing transports with filters"""
230
  type: Optional[str] = Field(None, description="Filter by transport type")
231
  status: Optional[str] = Field(None, description="Filter by status (active/inactive)")
232
- location_id: Optional[str] = Field(None, description="Filter by location ID")
233
  search: Optional[str] = Field(None, description="Search in name or code")
234
  skip: int = Field(0, ge=0, description="Pagination offset")
235
  limit: int = Field(100, ge=1, le=500, description="Items per page")
 
24
 
25
  # Coverage
26
  location_scope: str = Field(..., description="Location scope (all, selected)")
27
+ warehouse_ids: Optional[List[str]] = Field(None, description="Location IDs if scope is 'selected'")
28
  serviceable_regions: Optional[List[str]] = Field(None, description="Serviceable regions")
29
 
30
  notes: Optional[str] = Field(None, description="Additional notes")
 
108
  "supports_cod": True,
109
  "supports_tracking": True,
110
  "location_scope": "all",
111
+ "warehouse_ids": None,
112
  "serviceable_regions": ["Mumbai", "Delhi"],
113
  "notes": "Primary courier partner"
114
  }
 
130
  supports_tracking: Optional[bool] = Field(None, description="Tracking support")
131
 
132
  location_scope: Optional[str] = Field(None, description="Location scope")
133
+ warehouse_ids: Optional[List[str]] = Field(None, description="Location IDs")
134
  serviceable_regions: Optional[List[str]] = Field(None, description="Serviceable regions")
135
 
136
  notes: Optional[str] = Field(None, description="Additional notes")
 
214
  supports_tracking: bool = False
215
 
216
  location_scope: str
217
+ warehouse_ids: List[str] = Field(default_factory=list)
218
  serviceable_regions: List[str] = Field(default_factory=list)
219
 
220
  status: str
 
229
  """Schema for listing transports with filters"""
230
  type: Optional[str] = Field(None, description="Filter by transport type")
231
  status: Optional[str] = Field(None, description="Filter by status (active/inactive)")
232
+ warehouse_id: Optional[str] = Field(None, description="Filter by location ID")
233
  search: Optional[str] = Field(None, description="Search in name or code")
234
  skip: int = Field(0, ge=0, description="Pagination offset")
235
  limit: int = Field(100, ge=1, le=500, description="Items per page")
app/transports/services/service.py CHANGED
@@ -106,11 +106,11 @@ class TransportService:
106
  detail=f"Transport code {payload.code} already exists for this merchant"
107
  )
108
 
109
- # 3) Validate location_ids if location_scope is 'selected'
110
- if payload.location_scope == "selected" and (not payload.location_ids or len(payload.location_ids) == 0):
111
  raise HTTPException(
112
  status_code=status.HTTP_400_BAD_REQUEST,
113
- detail="location_ids required when location_scope is 'selected'"
114
  )
115
 
116
  # 4) Create transport model with timestamps
@@ -128,7 +128,7 @@ class TransportService:
128
  # Set defaults for optional fields
129
  transport_data["supports_cod"] = transport_data.get("supports_cod", False)
130
  transport_data["supports_tracking"] = transport_data.get("supports_tracking", False)
131
- transport_data["location_ids"] = transport_data.get("location_ids", [])
132
  transport_data["serviceable_regions"] = transport_data.get("serviceable_regions", [])
133
 
134
  transport_model = TransportModel(**transport_data)
@@ -203,13 +203,13 @@ class TransportService:
203
  update_data["updated_by"] = payload.updated_by
204
  update_data["updated_at"] = now.isoformat()
205
 
206
- # 4) Validate location_ids if location_scope is being updated to 'selected'
207
  if update_data.get("location_scope") == "selected":
208
- location_ids = update_data.get("location_ids") or existing.get("location_ids", [])
209
- if not location_ids or len(location_ids) == 0:
210
  raise HTTPException(
211
  status_code=status.HTTP_400_BAD_REQUEST,
212
- detail="location_ids required when location_scope is 'selected'"
213
  )
214
 
215
  # 5) Update in database
@@ -249,7 +249,7 @@ class TransportService:
249
  merchant_id: Optional[str] = None,
250
  transport_type: Optional[str] = None,
251
  status: Optional[str] = None,
252
- location_id: Optional[str] = None,
253
  search: Optional[str] = None,
254
  skip: int = 0,
255
  limit: int = 100,
@@ -262,7 +262,7 @@ class TransportService:
262
  merchant_id: Filter by merchant ID
263
  transport_type: Filter by transport type
264
  status: Filter by status
265
- location_id: Filter by location ID
266
  search: Search in name or code
267
  skip: Pagination offset
268
  limit: Page size
@@ -280,10 +280,10 @@ class TransportService:
280
  query["type"] = transport_type.lower()
281
  if status:
282
  query["status"] = status.lower()
283
- if location_id:
284
  query["$or"] = [
285
  {"location_scope": "all"},
286
- {"location_ids": location_id}
287
  ]
288
  if search:
289
  query["$or"] = [
@@ -314,7 +314,7 @@ class TransportService:
314
  "filters": {
315
  "type": transport_type,
316
  "status": status,
317
- "location_id": location_id,
318
  "search": search
319
  }
320
  })
 
106
  detail=f"Transport code {payload.code} already exists for this merchant"
107
  )
108
 
109
+ # 3) Validate warehouse_ids if location_scope is 'selected'
110
+ if payload.location_scope == "selected" and (not payload.warehouse_ids or len(payload.warehouse_ids) == 0):
111
  raise HTTPException(
112
  status_code=status.HTTP_400_BAD_REQUEST,
113
+ detail="warehouse_ids required when location_scope is 'selected'"
114
  )
115
 
116
  # 4) Create transport model with timestamps
 
128
  # Set defaults for optional fields
129
  transport_data["supports_cod"] = transport_data.get("supports_cod", False)
130
  transport_data["supports_tracking"] = transport_data.get("supports_tracking", False)
131
+ transport_data["warehouse_ids"] = transport_data.get("warehouse_ids", [])
132
  transport_data["serviceable_regions"] = transport_data.get("serviceable_regions", [])
133
 
134
  transport_model = TransportModel(**transport_data)
 
203
  update_data["updated_by"] = payload.updated_by
204
  update_data["updated_at"] = now.isoformat()
205
 
206
+ # 4) Validate warehouse_ids if location_scope is being updated to 'selected'
207
  if update_data.get("location_scope") == "selected":
208
+ warehouse_ids = update_data.get("warehouse_ids") or existing.get("warehouse_ids", [])
209
+ if not warehouse_ids or len(warehouse_ids) == 0:
210
  raise HTTPException(
211
  status_code=status.HTTP_400_BAD_REQUEST,
212
+ detail="warehouse_ids required when location_scope is 'selected'"
213
  )
214
 
215
  # 5) Update in database
 
249
  merchant_id: Optional[str] = None,
250
  transport_type: Optional[str] = None,
251
  status: Optional[str] = None,
252
+ warehouse_id: Optional[str] = None,
253
  search: Optional[str] = None,
254
  skip: int = 0,
255
  limit: int = 100,
 
262
  merchant_id: Filter by merchant ID
263
  transport_type: Filter by transport type
264
  status: Filter by status
265
+ warehouse_id: Filter by location ID
266
  search: Search in name or code
267
  skip: Pagination offset
268
  limit: Page size
 
280
  query["type"] = transport_type.lower()
281
  if status:
282
  query["status"] = status.lower()
283
+ if warehouse_id:
284
  query["$or"] = [
285
  {"location_scope": "all"},
286
+ {"warehouse_ids": warehouse_id}
287
  ]
288
  if search:
289
  query["$or"] = [
 
314
  "filters": {
315
  "type": transport_type,
316
  "status": status,
317
+ "warehouse_id": warehouse_id,
318
  "search": search
319
  }
320
  })
app/utils/stock_utils.py CHANGED
@@ -36,7 +36,7 @@ async def process_grn_stock_update(
36
  async def process_stock_adjustment(
37
  db: AsyncSession,
38
  merchant_id: str,
39
- location_id: str,
40
  sku: str,
41
  batch_no: str,
42
  adjustment_qty: float,
@@ -53,7 +53,7 @@ async def process_stock_adjustment(
53
 
54
  # Process adjustment
55
  ledger_id, success = await process_stock_adjustment(
56
- db, merchant_id, location_id, sku, batch_no,
57
  adjustment_qty, "damage", "Damaged goods", user_id, adjustment_id
58
  )
59
 
@@ -65,7 +65,7 @@ async def process_stock_adjustment(
65
  stock_service = StockService(db)
66
  return await stock_service.process_stock_adjustment(
67
  merchant_id=merchant_id,
68
- location_id=location_id,
69
  sku=sku,
70
  batch_no=batch_no,
71
  adjustment_qty=Decimal(str(adjustment_qty)),
@@ -79,7 +79,7 @@ async def process_stock_adjustment(
79
  async def get_current_stock(
80
  db: AsyncSession,
81
  merchant_id: str,
82
- location_id: str = None,
83
  sku: str = None
84
  ) -> List[dict]:
85
  """
@@ -92,13 +92,13 @@ async def get_current_stock(
92
  stock = await get_current_stock(db, merchant_id)
93
 
94
  # Get stock for specific location
95
- stock = await get_current_stock(db, merchant_id, location_id="WH001")
96
 
97
  # Get stock for specific SKU
98
  stock = await get_current_stock(db, merchant_id, sku="PROD001")
99
  """
100
  stock_service = StockService(db)
101
- return await stock_service.get_stock_summary(merchant_id, location_id, sku)
102
 
103
 
104
  async def get_stock_history(
@@ -127,7 +127,7 @@ async def get_stock_history(
127
  async def reserve_stock(
128
  db: AsyncSession,
129
  merchant_id: str,
130
- location_id: str,
131
  sku: str,
132
  batch_no: str,
133
  qty: float,
@@ -141,14 +141,14 @@ async def reserve_stock(
141
  from app.utils.stock_utils import reserve_stock
142
 
143
  # Reserve stock for sales order
144
- success = await reserve_stock(db, merchant_id, location_id, sku,
145
  batch_no, qty, sales_order_id, user_id)
146
  """
147
  from decimal import Decimal
148
 
149
  stock_service = StockService(db)
150
  return await stock_service.reserve_stock(
151
- merchant_id, location_id, sku, batch_no,
152
  Decimal(str(qty)), ref_id, created_by
153
  )
154
 
@@ -156,7 +156,7 @@ async def reserve_stock(
156
  async def release_stock_reservation(
157
  db: AsyncSession,
158
  merchant_id: str,
159
- location_id: str,
160
  sku: str,
161
  batch_no: str,
162
  qty: float,
 
36
  async def process_stock_adjustment(
37
  db: AsyncSession,
38
  merchant_id: str,
39
+ warehouse_id: str,
40
  sku: str,
41
  batch_no: str,
42
  adjustment_qty: float,
 
53
 
54
  # Process adjustment
55
  ledger_id, success = await process_stock_adjustment(
56
+ db, merchant_id, warehouse_id, sku, batch_no,
57
  adjustment_qty, "damage", "Damaged goods", user_id, adjustment_id
58
  )
59
 
 
65
  stock_service = StockService(db)
66
  return await stock_service.process_stock_adjustment(
67
  merchant_id=merchant_id,
68
+ warehouse_id=warehouse_id,
69
  sku=sku,
70
  batch_no=batch_no,
71
  adjustment_qty=Decimal(str(adjustment_qty)),
 
79
  async def get_current_stock(
80
  db: AsyncSession,
81
  merchant_id: str,
82
+ warehouse_id: str = None,
83
  sku: str = None
84
  ) -> List[dict]:
85
  """
 
92
  stock = await get_current_stock(db, merchant_id)
93
 
94
  # Get stock for specific location
95
+ stock = await get_current_stock(db, merchant_id, warehouse_id="WH001")
96
 
97
  # Get stock for specific SKU
98
  stock = await get_current_stock(db, merchant_id, sku="PROD001")
99
  """
100
  stock_service = StockService(db)
101
+ return await stock_service.get_stock_summary(merchant_id, warehouse_id, sku)
102
 
103
 
104
  async def get_stock_history(
 
127
  async def reserve_stock(
128
  db: AsyncSession,
129
  merchant_id: str,
130
+ warehouse_id: str,
131
  sku: str,
132
  batch_no: str,
133
  qty: float,
 
141
  from app.utils.stock_utils import reserve_stock
142
 
143
  # Reserve stock for sales order
144
+ success = await reserve_stock(db, merchant_id, warehouse_id, sku,
145
  batch_no, qty, sales_order_id, user_id)
146
  """
147
  from decimal import Decimal
148
 
149
  stock_service = StockService(db)
150
  return await stock_service.reserve_stock(
151
+ merchant_id, warehouse_id, sku, batch_no,
152
  Decimal(str(qty)), ref_id, created_by
153
  )
154
 
 
156
  async def release_stock_reservation(
157
  db: AsyncSession,
158
  merchant_id: str,
159
+ warehouse_id: str,
160
  sku: str,
161
  batch_no: str,
162
  qty: float,
docs/database/sql/stored_procedures/stock_management.sql CHANGED
@@ -4,7 +4,7 @@
4
  -- Simplified stock movement function with idempotency
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,
@@ -25,22 +25,22 @@ BEGIN
25
 
26
  -- Insert ledger entry (immutable audit trail)
27
  INSERT INTO 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
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
33
  );
34
 
35
  -- Stock snapshot upsert (create or update)
36
  INSERT INTO scm_stock (
37
- stock_id, merchant_id, location_id, sku, batch_no,
38
  qty_on_hand, qty_reserved, qty_available
39
  ) VALUES (
40
- gen_random_uuid(), p_merchant_id, p_location_id, p_sku, p_batch_no,
41
  p_qty, 0, p_qty
42
  )
43
- ON CONFLICT (merchant_id, location_id, sku, batch_no)
44
  DO UPDATE SET
45
  qty_on_hand = scm_stock.qty_on_hand + p_qty,
46
  qty_available = scm_stock.qty_available + p_qty,
@@ -66,7 +66,7 @@ BEGIN
66
  -- Apply individual stock movement
67
  SELECT apply_stock_movement(
68
  (v_movement->>'merchant_id')::VARCHAR(64),
69
- (v_movement->>'location_id')::VARCHAR(64),
70
  (v_movement->>'catalogue_id')::UUID,
71
  (v_movement->>'sku')::VARCHAR(64),
72
  (v_movement->>'batch_no')::VARCHAR(50),
@@ -100,7 +100,7 @@ $$;
100
  -- Function to reserve stock (for sales orders)
101
  CREATE OR REPLACE FUNCTION reserve_stock(
102
  p_merchant_id VARCHAR(64),
103
- p_location_id VARCHAR(64),
104
  p_sku VARCHAR(64),
105
  p_batch_no VARCHAR(50),
106
  p_qty NUMERIC(14,3),
@@ -117,7 +117,7 @@ BEGIN
117
  INTO v_available_qty
118
  FROM scm_stock
119
  WHERE merchant_id = p_merchant_id
120
- AND location_id = p_location_id
121
  AND sku = p_sku
122
  AND batch_no = p_batch_no;
123
 
@@ -133,7 +133,7 @@ BEGIN
133
  qty_available = qty_available - p_qty,
134
  last_updated_at = NOW()
135
  WHERE merchant_id = p_merchant_id
136
- AND location_id = p_location_id
137
  AND sku = p_sku
138
  AND batch_no = p_batch_no;
139
 
@@ -144,7 +144,7 @@ $$;
144
  -- Function to release stock reservation
145
  CREATE OR REPLACE FUNCTION release_stock_reservation(
146
  p_merchant_id VARCHAR(64),
147
- p_location_id VARCHAR(64),
148
  p_sku VARCHAR(64),
149
  p_batch_no VARCHAR(50),
150
  p_qty NUMERIC(14,3),
@@ -160,7 +160,7 @@ BEGIN
160
  qty_available = qty_on_hand - GREATEST(qty_reserved - p_qty, 0),
161
  last_updated_at = NOW()
162
  WHERE merchant_id = p_merchant_id
163
- AND location_id = p_location_id
164
  AND sku = p_sku
165
  AND batch_no = p_batch_no;
166
 
@@ -171,11 +171,11 @@ $$;
171
  -- Function to get current stock summary
172
  CREATE OR REPLACE FUNCTION get_stock_summary(
173
  p_merchant_id VARCHAR(64),
174
- p_location_id VARCHAR(64) DEFAULT NULL,
175
  p_sku VARCHAR(64) DEFAULT NULL
176
  ) RETURNS TABLE (
177
  stock_id UUID,
178
- location_id VARCHAR(64),
179
  catalogue_id UUID,
180
  sku VARCHAR(64),
181
  batch_no VARCHAR(50),
@@ -189,11 +189,11 @@ LANGUAGE plpgsql
189
  AS $$
190
  BEGIN
191
  RETURN QUERY
192
- SELECT s.stock_id, s.location_id, s.catalogue_id, s.sku, s.batch_no,
193
  s.qty_on_hand, s.qty_reserved, s.qty_available, s.uom, s.last_updated_at
194
  FROM scm_stock s
195
  WHERE s.merchant_id = p_merchant_id
196
- AND (p_location_id IS NULL OR s.location_id = p_location_id)
197
  AND (p_sku IS NULL OR s.sku = p_sku)
198
  ORDER BY s.sku, s.batch_no;
199
  END;
@@ -207,7 +207,7 @@ CREATE OR REPLACE FUNCTION get_stock_ledger(
207
  p_limit INTEGER DEFAULT 100
208
  ) RETURNS TABLE (
209
  ledger_id UUID,
210
- location_id VARCHAR(64),
211
  catalogue_id UUID,
212
  sku VARCHAR(64),
213
  batch_no VARCHAR(50),
@@ -225,7 +225,7 @@ LANGUAGE plpgsql
225
  AS $$
226
  BEGIN
227
  RETURN QUERY
228
- SELECT l.ledger_id, l.location_id, l.catalogue_id, l.sku, l.batch_no,
229
  l.txn_type, l.qty, l.uom, l.ref_type, l.ref_id, l.ref_no,
230
  l.remarks, l.created_by, l.created_at
231
  FROM scm_stock_ledger l
 
4
  -- Simplified stock movement function with idempotency
5
  CREATE OR REPLACE FUNCTION apply_stock_movement(
6
  p_merchant_id TEXT,
7
+ p_warehouse_id TEXT,
8
  p_sku TEXT,
9
  p_batch_no TEXT,
10
  p_qty NUMERIC,
 
25
 
26
  -- Insert ledger entry (immutable audit trail)
27
  INSERT INTO scm_stock_ledger (
28
+ ledger_id, merchant_id, warehouse_id, sku, batch_no,
29
  qty, txn_type, ref_type, ref_id, ref_no, created_by
30
  ) VALUES (
31
+ gen_random_uuid(), p_merchant_id, p_warehouse_id, p_sku, p_batch_no,
32
  p_qty, p_txn_type, p_ref_type, p_ref_id, p_ref_no, p_user
33
  );
34
 
35
  -- Stock snapshot upsert (create or update)
36
  INSERT INTO scm_stock (
37
+ stock_id, merchant_id, warehouse_id, sku, batch_no,
38
  qty_on_hand, qty_reserved, qty_available
39
  ) VALUES (
40
+ gen_random_uuid(), p_merchant_id, p_warehouse_id, p_sku, p_batch_no,
41
  p_qty, 0, p_qty
42
  )
43
+ ON CONFLICT (merchant_id, warehouse_id, sku, batch_no)
44
  DO UPDATE SET
45
  qty_on_hand = scm_stock.qty_on_hand + p_qty,
46
  qty_available = scm_stock.qty_available + p_qty,
 
66
  -- Apply individual stock movement
67
  SELECT apply_stock_movement(
68
  (v_movement->>'merchant_id')::VARCHAR(64),
69
+ (v_movement->>'warehouse_id')::VARCHAR(64),
70
  (v_movement->>'catalogue_id')::UUID,
71
  (v_movement->>'sku')::VARCHAR(64),
72
  (v_movement->>'batch_no')::VARCHAR(50),
 
100
  -- Function to reserve stock (for sales orders)
101
  CREATE OR REPLACE FUNCTION reserve_stock(
102
  p_merchant_id VARCHAR(64),
103
+ p_warehouse_id VARCHAR(64),
104
  p_sku VARCHAR(64),
105
  p_batch_no VARCHAR(50),
106
  p_qty NUMERIC(14,3),
 
117
  INTO v_available_qty
118
  FROM scm_stock
119
  WHERE merchant_id = p_merchant_id
120
+ AND warehouse_id = p_warehouse_id
121
  AND sku = p_sku
122
  AND batch_no = p_batch_no;
123
 
 
133
  qty_available = qty_available - p_qty,
134
  last_updated_at = NOW()
135
  WHERE merchant_id = p_merchant_id
136
+ AND warehouse_id = p_warehouse_id
137
  AND sku = p_sku
138
  AND batch_no = p_batch_no;
139
 
 
144
  -- Function to release stock reservation
145
  CREATE OR REPLACE FUNCTION release_stock_reservation(
146
  p_merchant_id VARCHAR(64),
147
+ p_warehouse_id VARCHAR(64),
148
  p_sku VARCHAR(64),
149
  p_batch_no VARCHAR(50),
150
  p_qty NUMERIC(14,3),
 
160
  qty_available = qty_on_hand - GREATEST(qty_reserved - p_qty, 0),
161
  last_updated_at = NOW()
162
  WHERE merchant_id = p_merchant_id
163
+ AND warehouse_id = p_warehouse_id
164
  AND sku = p_sku
165
  AND batch_no = p_batch_no;
166
 
 
171
  -- Function to get current stock summary
172
  CREATE OR REPLACE FUNCTION get_stock_summary(
173
  p_merchant_id VARCHAR(64),
174
+ p_warehouse_id VARCHAR(64) DEFAULT NULL,
175
  p_sku VARCHAR(64) DEFAULT NULL
176
  ) RETURNS TABLE (
177
  stock_id UUID,
178
+ warehouse_id VARCHAR(64),
179
  catalogue_id UUID,
180
  sku VARCHAR(64),
181
  batch_no VARCHAR(50),
 
189
  AS $$
190
  BEGIN
191
  RETURN QUERY
192
+ SELECT s.stock_id, s.warehouse_id, s.catalogue_id, s.sku, s.batch_no,
193
  s.qty_on_hand, s.qty_reserved, s.qty_available, s.uom, s.last_updated_at
194
  FROM scm_stock s
195
  WHERE s.merchant_id = p_merchant_id
196
+ AND (p_warehouse_id IS NULL OR s.warehouse_id = p_warehouse_id)
197
  AND (p_sku IS NULL OR s.sku = p_sku)
198
  ORDER BY s.sku, s.batch_no;
199
  END;
 
207
  p_limit INTEGER DEFAULT 100
208
  ) RETURNS TABLE (
209
  ledger_id UUID,
210
+ warehouse_id VARCHAR(64),
211
  catalogue_id UUID,
212
  sku VARCHAR(64),
213
  batch_no VARCHAR(50),
 
225
  AS $$
226
  BEGIN
227
  RETURN QUERY
228
+ SELECT l.ledger_id, l.warehouse_id, l.catalogue_id, l.sku, l.batch_no,
229
  l.txn_type, l.qty, l.uom, l.ref_type, l.ref_id, l.ref_no,
230
  l.remarks, l.created_by, l.created_at
231
  FROM scm_stock_ledger l
docs/database/sql/stored_procedures/stock_management_updated.sql CHANGED
@@ -4,7 +4,7 @@
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,
@@ -25,22 +25,22 @@ BEGIN
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),
@@ -69,7 +69,7 @@ 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,
@@ -119,7 +119,7 @@ $$;
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),
@@ -136,7 +136,7 @@ BEGIN
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
 
@@ -153,7 +153,7 @@ BEGIN
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
 
@@ -164,7 +164,7 @@ $$;
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),
@@ -181,7 +181,7 @@ BEGIN
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
 
@@ -192,11 +192,11 @@ $$;
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),
@@ -210,11 +210,11 @@ 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;
@@ -228,7 +228,7 @@ CREATE OR REPLACE FUNCTION get_stock_ledger(
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),
@@ -246,7 +246,7 @@ 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
 
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_warehouse_id TEXT,
8
  p_sku TEXT,
9
  p_batch_no TEXT,
10
  p_qty NUMERIC,
 
25
 
26
  -- Insert ledger entry (immutable audit trail)
27
  INSERT INTO trans.scm_stock_ledger (
28
+ ledger_id, merchant_id, warehouse_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_warehouse_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, warehouse_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_warehouse_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, warehouse_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),
 
69
  -- Apply individual stock movement
70
  PERFORM apply_stock_movement(
71
  (v_movement->>'merchant_id')::TEXT,
72
+ (v_movement->>'warehouse_id')::TEXT,
73
  (v_movement->>'sku')::TEXT,
74
  (v_movement->>'batch_no')::TEXT,
75
  (v_movement->>'qty')::NUMERIC,
 
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_warehouse_id VARCHAR(64),
123
  p_sku VARCHAR(64),
124
  p_batch_no VARCHAR(50),
125
  p_qty NUMERIC(14,3),
 
136
  INTO v_available_qty
137
  FROM trans.scm_stock
138
  WHERE merchant_id = p_merchant_id
139
+ AND warehouse_id = p_warehouse_id
140
  AND sku = p_sku
141
  AND batch_no = p_batch_no;
142
 
 
153
  updated_at = NOW(),
154
  last_updated_at = NOW()
155
  WHERE merchant_id = p_merchant_id
156
+ AND warehouse_id = p_warehouse_id
157
  AND sku = p_sku
158
  AND batch_no = p_batch_no;
159
 
 
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_warehouse_id VARCHAR(64),
168
  p_sku VARCHAR(64),
169
  p_batch_no VARCHAR(50),
170
  p_qty NUMERIC(14,3),
 
181
  updated_at = NOW(),
182
  last_updated_at = NOW()
183
  WHERE merchant_id = p_merchant_id
184
+ AND warehouse_id = p_warehouse_id
185
  AND sku = p_sku
186
  AND batch_no = p_batch_no;
187
 
 
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_warehouse_id VARCHAR(64) DEFAULT NULL,
196
  p_sku VARCHAR(64) DEFAULT NULL
197
  ) RETURNS TABLE (
198
  stock_id UUID,
199
+ warehouse_id VARCHAR(64),
200
  catalogue_id UUID,
201
  sku VARCHAR(64),
202
  batch_no VARCHAR(50),
 
210
  AS $$
211
  BEGIN
212
  RETURN QUERY
213
+ SELECT s.stock_id, s.warehouse_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_warehouse_id IS NULL OR s.warehouse_id = p_warehouse_id)
218
  AND (p_sku IS NULL OR s.sku = p_sku)
219
  ORDER BY s.sku, s.batch_no;
220
  END;
 
228
  p_limit INTEGER DEFAULT 100
229
  ) RETURNS TABLE (
230
  ledger_id UUID,
231
+ warehouse_id VARCHAR(64),
232
  catalogue_id UUID,
233
  sku VARCHAR(64),
234
  batch_no VARCHAR(50),
 
246
  AS $$
247
  BEGIN
248
  RETURN QUERY
249
+ SELECT l.ledger_id, l.warehouse_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
tests/test_merchant_catalogue_list.py CHANGED
@@ -159,7 +159,7 @@ class MerchantCatalogueListTester:
159
  # Create test scm_stock records
160
  stock_insert_query = """
161
  INSERT INTO scm_stock (
162
- stock_id, merchant_id, location_id, sku, batch_no,
163
  qty_on_hand, qty_reserved, qty_available, last_updated, created_at
164
  ) VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9)
165
  ON CONFLICT (stock_id) DO NOTHING
@@ -168,7 +168,7 @@ class MerchantCatalogueListTester:
168
  self.test_stock_data = [
169
  {
170
  "merchant_id": self.test_merchant_id,
171
- "location_id": "LOC-001",
172
  "sku": "SKU-SHAMPOO-001",
173
  "batch_no": "BATCH-001",
174
  "qty_on_hand": 100.0,
@@ -179,7 +179,7 @@ class MerchantCatalogueListTester:
179
  },
180
  {
181
  "merchant_id": self.test_merchant_id,
182
- "location_id": "LOC-002",
183
  "sku": "SKU-SHAMPOO-001",
184
  "batch_no": "BATCH-002",
185
  "qty_on_hand": 50.0,
@@ -190,7 +190,7 @@ class MerchantCatalogueListTester:
190
  },
191
  {
192
  "merchant_id": self.test_merchant_id,
193
- "location_id": "LOC-001",
194
  "sku": "SKU-CONDITIONER-001",
195
  "batch_no": "BATCH-003",
196
  "qty_on_hand": 75.0,
@@ -206,7 +206,7 @@ class MerchantCatalogueListTester:
206
  await self.pg_session.execute(
207
  stock_insert_query,
208
  [
209
- stock_data["merchant_id"], stock_data["location_id"],
210
  stock_data["sku"], stock_data["batch_no"],
211
  stock_data["qty_on_hand"], stock_data["qty_reserved"],
212
  stock_data["qty_available"], stock_data["last_updated"],
 
159
  # Create test scm_stock records
160
  stock_insert_query = """
161
  INSERT INTO scm_stock (
162
+ stock_id, merchant_id, warehouse_id, sku, batch_no,
163
  qty_on_hand, qty_reserved, qty_available, last_updated, created_at
164
  ) VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9)
165
  ON CONFLICT (stock_id) DO NOTHING
 
168
  self.test_stock_data = [
169
  {
170
  "merchant_id": self.test_merchant_id,
171
+ "warehouse_id": "LOC-001",
172
  "sku": "SKU-SHAMPOO-001",
173
  "batch_no": "BATCH-001",
174
  "qty_on_hand": 100.0,
 
179
  },
180
  {
181
  "merchant_id": self.test_merchant_id,
182
+ "warehouse_id": "LOC-002",
183
  "sku": "SKU-SHAMPOO-001",
184
  "batch_no": "BATCH-002",
185
  "qty_on_hand": 50.0,
 
190
  },
191
  {
192
  "merchant_id": self.test_merchant_id,
193
+ "warehouse_id": "LOC-001",
194
  "sku": "SKU-CONDITIONER-001",
195
  "batch_no": "BATCH-003",
196
  "qty_on_hand": 75.0,
 
206
  await self.pg_session.execute(
207
  stock_insert_query,
208
  [
209
+ stock_data["merchant_id"], stock_data["warehouse_id"],
210
  stock_data["sku"], stock_data["batch_no"],
211
  stock_data["qty_on_hand"], stock_data["qty_reserved"],
212
  stock_data["qty_available"], stock_data["last_updated"],