@things-factory/sales-base 4.3.677 → 4.3.679

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,53 +1,19 @@
1
- import {
2
- Arg,
3
- Ctx,
4
- Directive,
5
- Mutation,
6
- Resolver
7
- } from 'type-graphql'
8
- import {
9
- EntityManager,
10
- getConnection,
11
- getRepository,
12
- In,
13
- Repository
14
- } from 'typeorm'
1
+ import { Arg, Ctx, Directive, Mutation, Resolver } from 'type-graphql'
2
+ import { EntityManager, getConnection, getRepository, In, Repository } from 'typeorm'
15
3
 
16
4
  import { Bizplace } from '@things-factory/biz-base'
17
5
  import { logger } from '@things-factory/env'
18
- import {
19
- WebhookEventsEnum,
20
- webhookHandler
21
- } from '@things-factory/integration-base'
6
+ import { WebhookEventsEnum, webhookHandler } from '@things-factory/integration-base'
22
7
  import { ProductBundle } from '@things-factory/product-base'
23
- import {
24
- PartnerSetting,
25
- Setting
26
- } from '@things-factory/setting-base'
8
+ import { PartnerSetting, Setting } from '@things-factory/setting-base'
27
9
  import { Domain } from '@things-factory/shell'
28
10
 
29
- import {
30
- bulkReleaseGoodsAvailableItemsFunction,
31
- DraftReleaseGood,
32
- DraftReleaseGoodInfos,
33
- OrderProduct
34
- } from '../'
35
- import {
36
- DRAFT_RELEASE_ORDER_STATUS,
37
- ORDER_NUMBER_SETTING_KEY,
38
- ORDER_STATUS
39
- } from '../../constants'
11
+ import { bulkReleaseGoodsAvailableItemsFunction, DraftReleaseGood, DraftReleaseGoodInfos, OrderProduct } from '../'
12
+ import { DRAFT_RELEASE_ORDER_STATUS, ORDER_NUMBER_SETTING_KEY, ORDER_STATUS } from '../../constants'
40
13
  import { ValidationError } from '../../errors'
41
- import {
42
- InventoryUtil,
43
- OrderNoGenerator
44
- } from '../../utils'
14
+ import { InventoryUtil, OrderNoGenerator } from '../../utils'
45
15
  import { ReleaseGood } from '../release-good/release-good'
46
- import {
47
- bulkGenerateReleaseGood,
48
- confirmReleaseGood,
49
- receiveReleaseGood
50
- } from '../release-good/release-good-mutation'
16
+ import { bulkGenerateReleaseGood, confirmReleaseGood, receiveReleaseGood } from '../release-good/release-good-mutation'
51
17
  import { SuccessReleasedDraftOrder } from './draft-release-good-type'
52
18
 
53
19
  @Resolver(DraftReleaseGood)
@@ -171,18 +137,42 @@ export async function generateReleaseGoods(_generateReleaseGoodsRecord, context:
171
137
  }
172
138
  }
173
139
 
174
- let productInventory = await InventoryUtil.bizplaceProductInventory(
175
- bizplaces,
140
+ // Derive common warehouseCode across lines (if any) and collect batchIds
141
+ const warehouseCodes = Array.from(
142
+ new Set(
143
+ draftOrder.orderProducts
144
+ .map((op: any) => (op?.warehouseCode ? String(op.warehouseCode).trim() : ''))
145
+ .filter(Boolean)
146
+ )
147
+ )
148
+ const commonWarehouseCode = warehouseCodes.length === 1 ? warehouseCodes[0] : undefined
149
+ const batchIds = Array.from(
150
+ new Set(
151
+ draftOrder.orderProducts
152
+ .map((op: any) => (op?.batchId ? String(op.batchId).trim() : ''))
153
+ .filter(Boolean)
154
+ )
155
+ )
156
+
157
+ const filters: any[] = [
176
158
  {
177
- filters: [
178
- {
179
- name: 'productId',
180
- operator: 'in',
181
- value: productIds
182
- },
183
- { name: 'deleted_at', operator: 'eq', value: true }
184
- ]
159
+ name: 'productId',
160
+ operator: 'in',
161
+ value: productIds
185
162
  },
163
+ { name: 'deleted_at', operator: 'eq', value: true }
164
+ ]
165
+
166
+ if (commonWarehouseCode) {
167
+ filters.push({ name: 'warehouseCode', operator: 'eq', value: commonWarehouseCode })
168
+ if (batchIds.length > 0) {
169
+ filters.push({ name: 'batchId', operator: 'in', value: batchIds })
170
+ }
171
+ }
172
+
173
+ let productInventory = await InventoryUtil.bizplaceProductInventory(
174
+ bizplaces,
175
+ { filters },
186
176
  context,
187
177
  innerTx1
188
178
  )
@@ -268,7 +258,12 @@ export async function generateReleaseGoods(_generateReleaseGoodsRecord, context:
268
258
  releaseQty: itm.releaseQty,
269
259
  assignedQty: undefined,
270
260
  assignedUomValue: undefined,
271
- releaseUomValue: itm.releaseUomValue
261
+ releaseUomValue: itm.releaseUomValue,
262
+ refNo: draftOrder.refNo || '',
263
+ releaseDate: draftOrder.releaseDate || null,
264
+ // propagate filters to availability function
265
+ batchId: itm?.batchId || null,
266
+ warehouseCode: itm?.warehouseCode ? String(itm.warehouseCode).trim() : null
272
267
  }
273
268
  ]
274
269
  }
@@ -631,7 +626,12 @@ export async function generatePartialReleaseGoods(_generatePartialReleaseGoodsRe
631
626
  releaseQty: itm.releaseQty,
632
627
  assignedQty: undefined,
633
628
  assignedUomValue: undefined,
634
- releaseUomValue: itm.releaseUomValue
629
+ releaseUomValue: itm.releaseUomValue,
630
+ refNo: draftOrder.refNo || '',
631
+ releaseDate: draftOrder.releaseDate || null,
632
+ // propagate filters to availability function
633
+ batchId: itm?.batchId || null,
634
+ warehouseCode: itm?.warehouseCode ? String(itm.warehouseCode).trim() : null
635
635
  }
636
636
  ]
637
637
  }
@@ -499,7 +499,8 @@ export async function upsertDraftReleaseGoodProducts(
499
499
  ...existingOP,
500
500
  ...op,
501
501
  domain,
502
- batchId: '',
502
+ // Preserve batchId for product lines; bundles must not carry batchId
503
+ batchId: op?.warehouseCode ? (op?.batchId ? String(op.batchId).trim() : existingOP?.batchId || '') : '',
503
504
  packQty: 0,
504
505
  actualPackQty: 0,
505
506
  palletQty: 0,
@@ -271,35 +271,62 @@ export async function getDraftReleaseGoodFunction(_: any, context: any, id?: any
271
271
  const bizplaces: Bizplace[] = [foundPermittedBizplace, companyBizplace]
272
272
 
273
273
  let productInventory
274
- if (result.orderProducts.length > 0)
274
+ if (result.orderProducts.length > 0) {
275
+ // derive common warehouseCode (assumed same across all orderProducts when provided)
276
+ const warehouseCodes = Array.from(
277
+ new Set(
278
+ result.orderProducts
279
+ .map((op: any) => (op?.warehouseCode ? String(op.warehouseCode).trim() : ''))
280
+ .filter(Boolean)
281
+ )
282
+ )
283
+
284
+ const commonWarehouseCode = warehouseCodes.length === 1 ? warehouseCodes[0] : undefined
285
+
286
+ // collect batchIds (may be different per op)
287
+ const batchIds = Array.from(
288
+ new Set(result.orderProducts.map((op: any) => (op?.batchId ? String(op.batchId).trim() : '')).filter(Boolean))
289
+ )
290
+
291
+ const baseFilters: any[] = [
292
+ { name: 'bizplaceId', operator: 'eq', value: result.bizplace.id },
293
+ {
294
+ name: 'productId',
295
+ operator: 'in',
296
+ value: [
297
+ ...result.orderProducts
298
+ .filter(itm => itm?.product)
299
+ .map(itm => {
300
+ return itm.product.id
301
+ }),
302
+ ...result.orderProducts
303
+ .filter(itm => itm?.productBundle)
304
+ .map(itm => {
305
+ return itm.productBundle.productBundleSettings.map(itm => {
306
+ return itm.product.id
307
+ })
308
+ })
309
+ ]
310
+ }
311
+ ]
312
+
313
+ if (commonWarehouseCode) {
314
+ baseFilters.push({ name: 'warehouseCode', operator: 'eq', value: commonWarehouseCode })
315
+ }
316
+
317
+ if (batchIds.length > 0) {
318
+ baseFilters.push({ name: 'batchId', operator: 'in', value: batchIds })
319
+ }
320
+
275
321
  productInventory = await InventoryUtil.bizplaceProductInventory(
276
322
  bizplaces,
277
323
  {
278
- filters: [
279
- { name: 'bizplaceId', operator: 'eq', value: result.bizplace.id },
280
- {
281
- name: 'productId',
282
- operator: 'in',
283
- value: [
284
- ...result.orderProducts
285
- .filter(itm => itm?.product)
286
- .map(itm => {
287
- return itm.product.id
288
- }),
289
- ...result.orderProducts
290
- .filter(itm => itm?.productBundle)
291
- .map(itm => {
292
- return itm.productBundle.productBundleSettings.map(itm => {
293
- return itm.product.id
294
- })
295
- })
296
- ]
297
- }
298
- ]
324
+ filters: baseFilters
299
325
  },
300
326
  context,
301
327
  tx
302
328
  )
329
+ }
303
330
 
304
331
  result.orderProducts = result.orderProducts
305
332
  .map(itm => {
@@ -321,6 +348,7 @@ export async function getDraftReleaseGoodFunction(_: any, context: any, id?: any
321
348
  productDetail: itm.productDetail || {
322
349
  id: foundProductInv.productDetailId
323
350
  },
351
+ batchId: itm.batchId,
324
352
  groupType: foundProductInv ? foundProductInv.groupType : 'SINGLE',
325
353
  remainQty: foundProductInv ? foundProductInv.remainQty : 0,
326
354
  remainUomValue: foundProductInv ? foundProductInv.remainUomValue : 0,
@@ -352,6 +380,7 @@ export async function getDraftReleaseGoodFunction(_: any, context: any, id?: any
352
380
  id: foundProductInv.productDetailId
353
381
  },
354
382
  groupType: 'BUNDLE',
383
+ batchId: itm.batchId,
355
384
  releaseQty: itmBundleReleaseQty,
356
385
  remainQty: foundProductInv ? foundProductInv.remainQty : 0,
357
386
  remainUomValue: foundProductInv ? foundProductInv.remainUomValue : 0,
@@ -243,7 +243,7 @@ export class OrderProduct {
243
243
  expDate: Date
244
244
 
245
245
  @Column('date', { nullable: true })
246
- @Field( type => ScalarDate, { nullable: true })
246
+ @Field(type => ScalarDate, { nullable: true })
247
247
  adjustedExpDate: Date
248
248
 
249
249
  @ManyToOne(type => Warehouse, { nullable: true })
@@ -253,13 +253,13 @@ export class OrderProduct {
253
253
  @RelationId((orderProduct: OrderProduct) => orderProduct.warehouse)
254
254
  warehouseId: string
255
255
 
256
- @ManyToOne(type=> Warehouse, { nullable: true })
257
- @Field(type=> Warehouse, { nullable: true })
256
+ @ManyToOne(type => Warehouse, { nullable: true })
257
+ @Field(type => Warehouse, { nullable: true })
258
258
  adjustedWarehouse: Warehouse
259
259
 
260
260
  @RelationId((orderProduct: OrderProduct) => orderProduct.adjustedWarehouse)
261
261
  adjustedWarehouseId: string
262
-
262
+
263
263
  @ManyToOne(type => Location, { nullable: true })
264
264
  @Field(type => Location, { nullable: true })
265
265
  location: Location
@@ -458,6 +458,10 @@ export class OrderProduct {
458
458
  @Field({ nullable: true })
459
459
  remark: string
460
460
 
461
+ @Column({ nullable: true })
462
+ @Field({ nullable: true })
463
+ warehouseCode: string
464
+
461
465
  @Column({ nullable: true })
462
466
  @Field({ nullable: true })
463
467
  issue: string
@@ -951,6 +951,15 @@ export async function bulkReleaseGoodsAvailableItemsFunction(
951
951
  const companyBizplaceId: Bizplace = await getCompanyBizplace(null, null, bizplaceId)
952
952
 
953
953
  if (!rawReleaseGoods) return
954
+ // derive optional filters
955
+ const uniqueWarehouseCodes: string[] = Array.from(
956
+ new Set(
957
+ (rawReleaseGoods || []).map((r: any) => (r?.warehouseCode ? String(r.warehouseCode).trim() : '')).filter(Boolean)
958
+ )
959
+ )
960
+ const uniqueBatchIds: string[] = Array.from(
961
+ new Set((rawReleaseGoods || []).map((r: any) => (r?.batchId ? String(r.batchId).trim() : '')).filter(Boolean))
962
+ )
954
963
  const json_oi = JSON.stringify(
955
964
  rawReleaseGoods.map(raw => {
956
965
  return {
@@ -1009,6 +1018,7 @@ export async function bulkReleaseGoodsAvailableItemsFunction(
1009
1018
  `
1010
1019
  WITH inv AS (
1011
1020
  SELECT i.product_id, foo.product_detail_id, foo.sku, foo.product_info, i.packing_type, i.packing_size, i.uom,
1021
+ w.name as warehouse_name,
1012
1022
  ${useDetailedQuery ? ' i.batch_id, i.carton_id, i.expiration_date,' : ''}
1013
1023
  SUM(i.qty - COALESCE(i.locked_qty, 0)) - COALESCE(
1014
1024
  (
@@ -1035,6 +1045,7 @@ export async function bulkReleaseGoodsAvailableItemsFunction(
1035
1045
  p.is_inventory_decimal
1036
1046
  FROM inventories i
1037
1047
  INNER JOIN locations l ON i.location_id = l.id
1048
+ INNER JOIN warehouses w ON w.id = l.warehouse_id
1038
1049
  LEFT JOIN products p ON i.product_id = p.id
1039
1050
  INNER JOIN (
1040
1051
  SELECT rrg.product_id, rrg.product_detail_id, rrg.sku, rrg.product_info, rrg.packing_type, rrg.packing_size, rrg.uom
@@ -1052,12 +1063,21 @@ export async function bulkReleaseGoodsAvailableItemsFunction(
1052
1063
  AND i.obsolete = false
1053
1064
  AND i.transfer_qty <= 0
1054
1065
  AND i.transfer_uom_value <= 0
1055
- GROUP BY i.product_id, foo.product_detail_id, foo.sku, foo.product_info, i.packing_type, i.packing_size, i.uom, p.is_inventory_decimal
1066
+ AND ( $5::text IS NULL OR w.name = $5 )
1067
+ ${`AND ( $6::text IS NULL OR i.batch_id = $6 )`}
1068
+ GROUP BY i.product_id, foo.product_detail_id, foo.sku, foo.product_info, i.packing_type, i.packing_size, i.uom, p.is_inventory_decimal, w.name
1056
1069
  ${useDetailedQuery ? ', i.batch_id, i.carton_id, i.expiration_date' : ''}
1057
1070
  ORDER BY foo.sku, remain_qty DESC
1058
1071
  ) SELECT * FROM inv WHERE remain_qty > 0
1059
1072
  `,
1060
- [domain.id, bizplaceId, LOCATION_TYPE.QUARANTINE, LOCATION_TYPE.RESERVE]
1073
+ [
1074
+ domain.id,
1075
+ bizplaceId,
1076
+ LOCATION_TYPE.QUARANTINE,
1077
+ LOCATION_TYPE.RESERVE,
1078
+ uniqueWarehouseCodes.length === 1 ? uniqueWarehouseCodes[0] : null,
1079
+ uniqueBatchIds.length === 1 ? uniqueBatchIds[0] : null
1080
+ ]
1061
1081
  )
1062
1082
 
1063
1083
  await tx.query(`DROP TABLE raw_release_goods`)
@@ -1073,6 +1093,7 @@ export async function bulkReleaseGoodsAvailableItemsFunction(
1073
1093
  uom: item.uom,
1074
1094
  remainQty: item.remain_qty,
1075
1095
  remainUomValue: item.remain_uom_value,
1096
+ warehouseCode: item.warehouse_name || null,
1076
1097
  batchId: item.batch_id ? item.batch_id : null,
1077
1098
  cartonId: item.carton_id ? item.carton_id : null,
1078
1099
  expirationDate: item.expiration_date ? _getStdDateStr(new Date(item.expiration_date)) : null,
@@ -1096,6 +1117,11 @@ function _extractData(rawData, validatedData) {
1096
1117
  comparison.push('batchId')
1097
1118
  if (!data.length) errMsg = errMsg ? errMsg : 'batch no not matched'
1098
1119
  }
1120
+ if (raw.warehouseCode) {
1121
+ data = data.filter(val => val.warehouseCode === String(raw.warehouseCode).trim())
1122
+ comparison.push('warehouseCode')
1123
+ if (!data.length) errMsg = errMsg ? errMsg : 'warehouse code not matched'
1124
+ }
1099
1125
  if (raw.cartonId) {
1100
1126
  data = data.filter(val => val.cartonId === raw.cartonId)
1101
1127
  comparison.push('cartonId')
@@ -1133,20 +1159,17 @@ function _extractData(rawData, validatedData) {
1133
1159
 
1134
1160
  raw.assignedQty =
1135
1161
  Math.round(
1136
- (data[idx].remainQty >= raw.releaseQty
1137
- ? raw.releaseQty > 0
1138
- ? raw.releaseQty
1139
- : 0
1140
- : data[idx].remainQty) * 1000
1162
+ (data[idx].remainQty >= raw.releaseQty ? (raw.releaseQty > 0 ? raw.releaseQty : 0) : data[idx].remainQty) *
1163
+ 1000
1141
1164
  ) / 1000
1142
1165
 
1143
1166
  raw.assignedUomValue =
1144
1167
  Math.round(
1145
1168
  (data[idx].remainUomValue >= releaseUomValue
1146
- ? releaseUomValue > 0
1147
- ? releaseUomValue
1148
- : 0
1149
- : data[idx].remainUomValue) * 1000
1169
+ ? releaseUomValue > 0
1170
+ ? releaseUomValue
1171
+ : 0
1172
+ : data[idx].remainUomValue) * 1000
1150
1173
  ) / 1000
1151
1174
 
1152
1175
  // deduct qty & uomValue from validateData
@@ -1190,7 +1213,7 @@ function _extractData(rawData, validatedData) {
1190
1213
  errors.push('insufficient stock')
1191
1214
  }
1192
1215
  if (!dateRegex.test(raw.releaseDate)) {
1193
- errors.push('invalid release date format. please use dd/mm/yyyy')
1216
+ errors.push('invalid release date format. please use yyyy-mm-dd')
1194
1217
  }
1195
1218
  if (raw.releaseDate === '') {
1196
1219
  errors.push('release date is empty')
@@ -1213,7 +1236,7 @@ function _extractData(rawData, validatedData) {
1213
1236
  errors.push('invalid paid amount')
1214
1237
  }
1215
1238
  if (raw?.expirationDate != null && !dateRegex.test(raw?.expirationDate)) {
1216
- errors.push('invalid expiration date format. please use dd/mm/yyyy')
1239
+ errors.push('invalid expiration date format. please use yyyy-mm-dd')
1217
1240
  }
1218
1241
  if (raw.airwayBill && raw.lmdOption === true) {
1219
1242
  errors.push('kindly remove AWB as LMD is marked as true')
@@ -59,6 +59,25 @@ export const InventoryUtil = {
59
59
  let inventoryBizplaceFilter = filters.find(itm => itm.name == 'bizplaceId')
60
60
  let deletedAt = filters.find(filter => filter.name == 'deleted_at')
61
61
 
62
+ const warehouseCodeFilter = filters.find(itm => itm.name == 'warehouseCode')
63
+ const batchIdFilter = filters.find(itm => itm.name == 'batchId')
64
+ const sanitizedWarehouseCode = warehouseCodeFilter
65
+ ? String(warehouseCodeFilter.value).trim().replace(/'/g, "''")
66
+ : null
67
+
68
+ let batchFilterClause = ''
69
+ // Apply strict batch filter only when warehouseCode is provided
70
+ if (sanitizedWarehouseCode && batchIdFilter) {
71
+ const op = (batchIdFilter.operator || '').toLowerCase()
72
+ const val = batchIdFilter.value
73
+ if (op === 'in' && Array.isArray(val)) {
74
+ const list = val.map(v => `'${String(v).trim().replace(/'/g, "''")}'`).join(',')
75
+ batchFilterClause = `AND i.batch_id IN (${list})`
76
+ } else if (val !== undefined && val !== null && String(val).trim() !== '') {
77
+ batchFilterClause = `AND i.batch_id = '${String(val).trim().replace(/'/g, "''")}'`
78
+ }
79
+ }
80
+
62
81
  let queryStrings = `
63
82
  CREATE TEMP TABLE temp_inventory_product_group ON COMMIT DROP AS (
64
83
  SELECT * FROM (
@@ -125,12 +144,15 @@ export const InventoryUtil = {
125
144
  LEFT JOIN (
126
145
  SELECT i.* FROM inventories i
127
146
  INNER JOIN locations l2 ON i.location_id = l2.id AND i.domain_id = l2.domain_id
147
+ INNER JOIN warehouses w ON w.id = l2.warehouse_id
128
148
  WHERE l2.type NOT IN ('${LOCATION_TYPE.QUARANTINE}', '${LOCATION_TYPE.RESERVE}', '${
129
149
  LOCATION_TYPE.DAMAGE
130
150
  }')
131
151
  AND i.obsolete = false
132
152
  AND i.domain_id = $1 AND i.status = 'STORED'
133
153
  ${inventoryBizplaceFilter ? `AND i.bizplace_id = '${inventoryBizplaceFilter.value}'` : ``}
154
+ ${sanitizedWarehouseCode ? `AND w.name = '${sanitizedWarehouseCode}'` : ``}
155
+ ${batchFilterClause}
134
156
  ) i ON i.product_detail_id = pd.id
135
157
  LEFT JOIN oi ON i.batch_id = oi.batch_id AND p.name = oi.product_name AND i.packing_type = oi.packing_type AND i.packing_size = oi.packing_size AND i.uom = oi.uom
136
158
  LEFT JOIN (
@@ -189,12 +211,14 @@ export const InventoryUtil = {
189
211
  LEFT JOIN inventories i2 ON i2.product_id = pbs.product_id AND i2.domain_id = $1 AND i2.status = 'STORED'
190
212
  LEFT JOIN product_detail_stocks pds2 ON pds2.product_detail_id = pbs.product_detail_id
191
213
  INNER JOIN locations l2 ON i2.location_id = l2.id
214
+ INNER JOIN warehouses w2 ON w2.id = l2.warehouse_id
192
215
  LEFT JOIN oi ON oi.product_id = i2.product_id
193
216
  WHERE l2.type NOT IN ('${LOCATION_TYPE.QUARANTINE}', '${LOCATION_TYPE.RESERVE}', '${
194
217
  LOCATION_TYPE.DAMAGE
195
218
  }')
196
219
  and i2.obsolete = false
197
220
  ${inventoryBizplaceFilter ? `AND i2.bizplace_id = '${inventoryBizplaceFilter.value}'` : ``}
221
+ ${sanitizedWarehouseCode ? `AND w2.name = '${sanitizedWarehouseCode}'` : ``}
198
222
  GROUP BY
199
223
  pbs.product_id,
200
224
  pbs.product_bundle_id,
@@ -848,7 +872,8 @@ export const InventoryUtil = {
848
872
  preferLocation: string = null,
849
873
  recall: boolean = null,
850
874
  cartonId?: string,
851
- expirationDate?: Date
875
+ expirationDate?: Date,
876
+ warehouseName?: string
852
877
  ): Promise<OrderInventory[]> {
853
878
  let strictProduct = 'false'
854
879
 
@@ -880,6 +905,7 @@ export const InventoryUtil = {
880
905
 
881
906
  let qb: SelectQueryBuilder<Inventory> = trxMgr.getRepository(Inventory).createQueryBuilder('iv')
882
907
  qb.innerJoinAndSelect('iv.location', 'loc')
908
+ .innerJoin('warehouses', 'w', 'w.id = loc.warehouse_id')
883
909
  .innerJoin('warehouse_inventory_assignment_rankings', 'wiar', '"wiar"."location_type" = "loc"."type"')
884
910
  .innerJoin('iv.product', 'p')
885
911
  .andWhere('"iv"."domain_id" = :domainId')
@@ -917,6 +943,9 @@ export const InventoryUtil = {
917
943
  if (preferLocation) {
918
944
  qb.andWhere('"loc"."type" = :preferLocation', { preferLocation })
919
945
  }
946
+ if (warehouseName) {
947
+ qb.andWhere('w.name = :warehouseName', { warehouseName: String(warehouseName).trim() })
948
+ }
920
949
  if (strictProduct == 'true' || preferLocation == 'STORAGE') {
921
950
  qb.andWhere('"iv"."product_detail_id" = :productDetailId', { productDetailId: productDetail.id })
922
951
  } else {
@@ -1075,8 +1104,17 @@ async function getConditions(
1075
1104
  break
1076
1105
 
1077
1106
  case 'batchId':
1078
- let batchId = `${value}`
1079
- whereClause += `AND LOWER("batchId") LIKE '${batchId.replace(`'`, `''`).toLowerCase()}'`
1107
+ // Only apply to outer whereClause when the consumer's temp table includes "batchId"
1108
+ if (hasRemainingQty) {
1109
+ if (operator === 'in' && Array.isArray(value)) {
1110
+ const list = value.map((v: string) => `'${String(v).trim().replace(/'/g, "''")}'`).join(',')
1111
+ whereClause += `AND "batchId" IN (${list})`
1112
+ } else {
1113
+ let batchId = `${value}`
1114
+ whereClause += `AND LOWER("batchId") LIKE '${batchId.replace(`'`, `''`).toLowerCase()}'`
1115
+ }
1116
+ }
1117
+ // For consumers without batchId column (e.g., bizplaceProductInventory), batch filtering is handled inside subqueries
1080
1118
  break
1081
1119
 
1082
1120
  case 'batchIdRef':