@things-factory/worksheet-base 4.3.701 → 4.3.708

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.
@@ -239,6 +239,7 @@ export class PickingWorksheetController extends VasWorksheetController {
239
239
  'releaseGood',
240
240
  'releaseGood.bizplace',
241
241
  'releaseGood.domain',
242
+ 'releaseGood.deliverTo',
242
243
  'releaseGood.lastMileDelivery',
243
244
  'releaseGood.bizplace.domain',
244
245
  'domain',
@@ -517,6 +518,7 @@ export class PickingWorksheetController extends VasWorksheetController {
517
518
  'worksheetDetails.targetProduct.product',
518
519
  'worksheetDetails.targetProduct.productDetail',
519
520
  'worksheetDetails.targetProduct.releaseGood',
521
+ 'worksheetDetails.targetProduct.releaseGood.deliverTo',
520
522
  'worksheetDetails.targetInventory',
521
523
  'worksheetDetails.targetInventory.releaseGood',
522
524
  'worksheetDetails.targetInventory.releaseGood.domain',
@@ -2423,6 +2425,11 @@ export class PickingWorksheetController extends VasWorksheetController {
2423
2425
  }
2424
2426
  }
2425
2427
 
2428
+ // if deliverTo contact point exists and has releaseShelfLife, use it as override for outbound shelf life
2429
+ const releaseShelfLife = (releaseGood?.deliverTo as any)?.releaseShelfLife
2430
+ const outboundShelfLifeOverride =
2431
+ releaseGood?.deliverTo && releaseShelfLife != null && releaseShelfLife !== 0 ? Number(releaseShelfLife) : null
2432
+
2426
2433
  let assignedOrderInventories: OrderInventory[] = await InventoryUtil.autoAssignInventoryForRelease(
2427
2434
  op,
2428
2435
  op.productDetail.product,
@@ -2436,7 +2443,8 @@ export class PickingWorksheetController extends VasWorksheetController {
2436
2443
  undefined,
2437
2444
  undefined,
2438
2445
  undefined,
2439
- (op as any)?.warehouseCode
2446
+ (op as any)?.warehouseCode,
2447
+ outboundShelfLifeOverride
2440
2448
  )
2441
2449
 
2442
2450
  assignedOrderInventories = assignedOrderInventories.map(aoi => {
@@ -2522,7 +2530,11 @@ export class PickingWorksheetController extends VasWorksheetController {
2522
2530
  locationSortingRules: inventoryAssignmentSetting ? JSON.parse(inventoryAssignmentSetting.value) : false,
2523
2531
  pickingStrategy: orderProducts[i].product.pickingStrategy,
2524
2532
  warehouseName:
2525
- (orderProducts[i] as any)?.warehouseCode || (orderProducts[i] as any)?.warehouse?.name || undefined
2533
+ (orderProducts[i] as any)?.warehouseCode || (orderProducts[i] as any)?.warehouse?.name || undefined,
2534
+ releaseShelfLifeOverride: (() => {
2535
+ const releaseShelfLife = (orderProducts[i]?.releaseGood as any)?.deliverTo?.releaseShelfLife
2536
+ return releaseShelfLife != null && releaseShelfLife !== 0 ? Number(releaseShelfLife) : null
2537
+ })()
2526
2538
  },
2527
2539
  this.domain,
2528
2540
  this.trxMgr
@@ -26,15 +26,34 @@ export async function renderFmGRN({ grnNo, timezoneOffSet }, context: any) {
26
26
  // 2. find grn
27
27
  const foundGRN: GoodsReceivalNote = await getRepository(GoodsReceivalNote).findOne({
28
28
  where: { domain, name: grnNo },
29
- relations: ['domain', 'bizplace', 'bizplace.domain', 'bizplace.company', 'arrivalNotice']
29
+ relations: [
30
+ 'domain',
31
+ 'bizplace',
32
+ 'bizplace.domain',
33
+ 'bizplace.company',
34
+ 'arrivalNotice',
35
+ 'arrivalNotice.creator',
36
+ 'updater'
37
+ ]
30
38
  })
31
39
 
40
+ if (!foundGRN) {
41
+ throw new Error(`GRN not found for no: ${grnNo}`)
42
+ }
43
+
32
44
  // 3. find GAN
33
45
  const foundGAN: ArrivalNotice = foundGRN.arrivalNotice
34
46
  const ownRefNo = foundGAN.refNo
35
47
 
36
48
  const foundInventoryItem: InventoryItem[] = await getRepository(InventoryItem).query(
37
- `select row_number() over (partition by p.sku) as "seq",p.sku,p.brand_sku, ii.serial_number from inventory_items ii left join products p on ii.product_id = p.id where inbound_order_id = '${foundGAN.id}' group by p.sku,ii.serial_number,p.brand_sku`
49
+ `select row_number() over (partition by p.sku) as "seq",
50
+ p.sku,
51
+ p.brand_sku,
52
+ ii.serial_number
53
+ from inventory_items ii
54
+ left join products p on ii.product_id = p.id
55
+ where inbound_order_id = '${foundGAN.id}'
56
+ group by p.sku, ii.serial_number, p.brand_sku`
38
57
  )
39
58
 
40
59
  // 4. find customer bizplace
@@ -51,27 +70,29 @@ export async function renderFmGRN({ grnNo, timezoneOffSet }, context: any) {
51
70
  relations: ['company']
52
71
  })
53
72
 
54
- const qbReducedInventory = getRepository(ReducedInventoryHistory)
73
+ // ReducedInventoryHistory -> ONE ROW PER PALLET
74
+ const inboundInventories: any[] = await getRepository(ReducedInventoryHistory)
55
75
  .createQueryBuilder('ivh')
56
- .select('product_id', 'productId')
57
- .addSelect('batch_id', 'batchId')
58
- .addSelect('reusable_pallet_id', 'reusablePalletId')
59
- .addSelect('packing_type', 'packingType')
60
- .addSelect('sum(qty)', 'qty')
61
- .addSelect('sum(uom_value)', 'uomValue')
62
- .addSelect('uom', 'uom')
63
- .addSelect('expiration_date', 'expiryDate')
64
- .addSelect('count(distinct pallet_id)', 'pallet')
65
- .addSelect(`string_agg(distinct pallet_id::varchar,', ' order by pallet_id::varchar)`, 'palletId')
76
+ .select('ivh.product_id', 'productId')
77
+ .addSelect('ivh.batch_id', 'batchId')
78
+ .addSelect('ivh.reusable_pallet_id', 'reusablePalletId')
79
+ .addSelect('ivh.packing_type', 'packingType')
80
+ .addSelect('ivh.uom', 'uom')
81
+ .addSelect('ivh.pallet_id', 'palletId')
82
+ .addSelect('ivh.expiration_date', 'expiryDate')
83
+ .addSelect('SUM(ivh.qty)', 'qty')
84
+ .addSelect('SUM(ivh.uom_value)', 'uomValue')
85
+ .addSelect('1', 'palletQty') // one pallet per row
66
86
  .where('ivh.domain_id = :domainId', { domainId: domain.id })
67
87
  .andWhere('ivh.ref_order_id = :refOrderId', { refOrderId: foundGAN.id })
68
88
  .andWhere(`ivh.transaction_type = 'UNLOADING'`)
69
- .groupBy('reusable_pallet_id')
70
- .addGroupBy('product_id')
71
- .addGroupBy('batch_id')
72
- .addGroupBy('packing_type')
73
- .addGroupBy('uom')
74
- .addGroupBy('expiration_date')
89
+ .groupBy('ivh.product_id')
90
+ .addGroupBy('ivh.batch_id')
91
+ .addGroupBy('ivh.reusable_pallet_id')
92
+ .addGroupBy('ivh.packing_type')
93
+ .addGroupBy('ivh.uom')
94
+ .addGroupBy('ivh.pallet_id')
95
+ .addGroupBy('ivh.expiration_date')
75
96
  .getRawMany()
76
97
 
77
98
  // 5. find domain contact point
@@ -79,7 +100,6 @@ export async function renderFmGRN({ grnNo, timezoneOffSet }, context: any) {
79
100
  where: { domain, bizplace: domainBizplace }
80
101
  })
81
102
 
82
- // 5. find domain contact point
83
103
  const foundPartnerCP: ContactPoint = await getRepository(ContactPoint).findOne({
84
104
  where: { domain, bizplace: partnerBiz }
85
105
  })
@@ -98,7 +118,7 @@ export async function renderFmGRN({ grnNo, timezoneOffSet }, context: any) {
98
118
 
99
119
  const targetProducts: OrderProduct[] = await getRepository(OrderProduct).find({
100
120
  where: { domain, arrivalNotice: foundGAN, actualPalletQty: Not(IsNull()), actualPackQty: Not(IsNull()) },
101
- relations: ['product']
121
+ relations: ['product', 'productDetail']
102
122
  })
103
123
 
104
124
  // 7. find grn template based on category
@@ -129,8 +149,6 @@ export async function renderFmGRN({ grnNo, timezoneOffSet }, context: any) {
129
149
  }
130
150
  })
131
151
 
132
- const inboundInventories: any[] = await qbReducedInventory
133
-
134
152
  const template = await STORAGE.readFile(foundTemplate.path, 'utf-8')
135
153
 
136
154
  let logo = null
@@ -148,28 +166,122 @@ export async function renderFmGRN({ grnNo, timezoneOffSet }, context: any) {
148
166
  cop = 'data:' + foundSignature.mimetype + ';base64,' + (await STORAGE.readFile(foundCop.path, 'base64'))
149
167
  }
150
168
 
169
+ // serial number aggregation
151
170
  const filterInventoryItem = _.groupBy(foundInventoryItem, i => i.sku)
152
-
153
- const tempIndexArr = []
154
-
155
- const tempTotalQuantity = []
171
+ const tempIndexArr: number[] = []
172
+ const tempTotalQuantity: number[] = []
156
173
 
157
174
  Object.keys(filterInventoryItem).forEach(k => {
158
175
  const tempIndex = foundInventoryItem.findIndex(i => {
159
176
  return i.sku == k
160
177
  })
161
-
162
178
  tempIndexArr.push(tempIndex)
163
179
  })
164
180
 
165
181
  Object.values(filterInventoryItem).forEach(k => {
166
- const tempQuantity = k.length
167
-
182
+ const tempQuantity = (k as any[]).length
168
183
  tempTotalQuantity.push(tempQuantity)
169
184
  })
170
185
 
171
186
  tempIndexArr.forEach((t, index) => {
172
- if (t >= 0) foundInventoryItem[t].totalQuantity = tempTotalQuantity[index]
187
+ if (t >= 0) (foundInventoryItem[t] as any).totalQuantity = tempTotalQuantity[index]
188
+ })
189
+
190
+ // =========================
191
+ // Build pallet_list (per pallet)
192
+ // =========================
193
+
194
+ const pallet_list = inboundInventories.map((inv, idx) => {
195
+ const item = targetProducts.find(
196
+ tp =>
197
+ tp.product.id === inv.productId &&
198
+ tp.batchId === inv.batchId &&
199
+ tp.packingType === inv.packingType &&
200
+ tp.uom === inv.uom &&
201
+ tp.palletId === inv.palletId
202
+ )
203
+
204
+ const qty = Number(inv.qty) || (item ? item.actualPackQty : 0)
205
+ const uomValue = Number(inv.uomValue) || (item ? item.uomValue * qty : 0)
206
+ const volume = item && item.productDetail.volume ? item.productDetail.volume * qty : 0
207
+ const expiryDate = inv.expiryDate ? new Date(inv.expiryDate).toISOString().split('T')[0] : ''
208
+
209
+ return {
210
+ list_no: idx + 1,
211
+ _uomValue: uomValue, // internal for grouping later
212
+ product_sku: item ? `${item.product.sku}` : '',
213
+ product_brand_sku: item ? `${item.product.brandSku}` : '',
214
+ product_name: item ? `${item.product.name}(${item.product.description})` : '',
215
+ product_desc: item ? item.product.description : '',
216
+ product_nameOnly: item ? item.product.name : '',
217
+ product_type: inv.packingType,
218
+ product_size: item ? item.packingSize : null,
219
+ product_batch: inv.batchId,
220
+ product_volume: volume,
221
+ batch_id_ref: item?.batchIdRef || '',
222
+ pallet_id: inv.palletId,
223
+ pallet_qty: Number(inv.palletQty) || (item?.actualPalletQty ?? 1),
224
+ pack_qty: qty,
225
+ product_qty: qty,
226
+ discrepancy_qty: item ? item.packQty - item.actualPackQty : 0,
227
+ product_unit_uom_value: item ? `${Math.round(item.uomValue * 100) / 100}` : '',
228
+ product_total_uom_value: `${Math.round(uomValue * 100) / 100}`,
229
+ product_uom: inv.uom,
230
+ product_gross_weight: item ? item.product.grossWeight : null,
231
+ unit_price: item ? item.unitPrice : null,
232
+ expiry_date: expiryDate,
233
+ manufacture_date: item ? item.manufactureDate : null,
234
+ reusable_pallet_id: inv.reusablePalletId || '',
235
+ remark: item ? (item.remark ? item.remark : '') + (item.issue ? ' [Issue]: ' + item.issue : '') : ''
236
+ }
237
+ })
238
+
239
+ // =========================
240
+ // Build product_list (grouped view)
241
+ // =========================
242
+
243
+ const groupedByProduct = _.groupBy(pallet_list, pl =>
244
+ [
245
+ pl.product_sku,
246
+ pl.product_batch,
247
+ pl.product_type,
248
+ pl.product_uom,
249
+ pl.expiry_date // keep expiries separate; remove from key if you want to merge
250
+ ].join('|')
251
+ )
252
+
253
+ const product_list = Object.values(groupedByProduct).map((rows: any[], idx) => {
254
+ const first = rows[0]
255
+ const totalQty = rows.reduce((sum, r) => sum + (r.product_qty || 0), 0)
256
+ const totalPalletQty = rows.reduce((sum, r) => sum + (r.pallet_qty || 0), 0)
257
+ const totalUomValue = rows.reduce((sum, r) => sum + (r._uomValue || 0), 0)
258
+
259
+ return {
260
+ list_no: idx + 1,
261
+ product_sku: first.product_sku,
262
+ product_brand_sku: first.product_brand_sku,
263
+ product_name: first.product_name,
264
+ product_desc: first.product_desc,
265
+ product_nameOnly: first.product_nameOnly,
266
+ product_type: first.product_type,
267
+ product_size: first.product_size,
268
+ product_batch: first.product_batch,
269
+ product_volume: first.product_volume,
270
+ batch_id_ref: first.batch_id_ref,
271
+ pallet_qty: totalPalletQty,
272
+ pack_qty: totalQty,
273
+ product_qty: totalQty,
274
+ discrepancy_qty: first.discrepancy_qty, // per-product discrepancy if needed
275
+ product_unit_uom_value: first.product_unit_uom_value,
276
+ product_total_uom_value: `${Math.round(totalUomValue * 100) / 100}`,
277
+ product_uom: first.product_uom,
278
+ product_gross_weight: first.product_gross_weight,
279
+ unit_price: first.unit_price,
280
+ expiry_date: first.expiry_date,
281
+ manufacture_date: first.manufacture_date,
282
+ reusable_pallet_id: first.reusable_pallet_id,
283
+ remark: first.remark
284
+ }
173
285
  })
174
286
 
175
287
  const data = {
@@ -192,7 +304,11 @@ export async function renderFmGRN({ grnNo, timezoneOffSet }, context: any) {
192
304
  warehouse_fax: foundCP.fax,
193
305
  warehouse_email: foundCP.email,
194
306
  order_no: foundGRN.name,
307
+ grn_received_by: foundGRN.updater.name,
308
+ grn_received_date: foundGRN.createdAt ? DateTimeConverter.date(foundGRN.createdAt) : '',
195
309
  gan_no: foundGAN.name,
310
+ gan_created_by: foundGAN.creator.name,
311
+ gan_created_date: foundGAN.createdAt ? DateTimeConverter.date(foundGAN.createdAt) : '',
196
312
  gan_accepted_at: foundGAN.acceptedAt ? DateTimeConverter.datetime(foundGAN.acceptedAt, timezoneOffSet) : '',
197
313
  unload_date: DateTimeConverter.date(foundWS.endedAt),
198
314
  ref_no: ownRefNo ? `${foundGAN.name} / ${foundGAN.refNo}` : `${foundGAN.name}`,
@@ -207,49 +323,8 @@ export async function renderFmGRN({ grnNo, timezoneOffSet }, context: any) {
207
323
  account_no: foundPartnerCP ? foundPartnerCP.accountNo : '',
208
324
  unloaded_by: foundWS ? foundWS.updater.name : '',
209
325
  putaway_by: foundPutawayWS ? foundPutawayWS.updater.name : '',
210
- product_list: targetProducts
211
- .reduce((acc, item) => {
212
- acc.push(
213
- ...inboundInventories
214
- .filter(
215
- ih =>
216
- ih.productId == item.product.id &&
217
- ih.batchId == item.batchId &&
218
- ih.packingType == item.packingType &&
219
- ih.uom == item.uom
220
- )
221
- .map(unloadInvHistory => {
222
- return {
223
- product_sku: `${item.product.sku}`,
224
- product_brand_sku: `${item.product.brandSku}`,
225
- product_name: `${item.product.name}(${item.product.description})`,
226
- product_desc: item.product.description,
227
- product_nameOnly: item.product.name,
228
- product_type: item.packingType,
229
- product_size: item.packingSize,
230
- product_batch: item.batchId,
231
- batch_id_ref: item?.batchIdRef ? item.batchIdRef : '',
232
- pallet_qty: item.actualPalletQty,
233
- product_qty: unloadInvHistory.qty || item.actualPackQty,
234
- product_pack_qty: item.packQty,
235
- descrepancy_qty: (unloadInvHistory.qty || item.actualPackQty) - item.packQty,
236
- product_unit_uom_value: `${Math.round(item.uomValue * 100) / 100}`,
237
- product_total_uom_value: `${
238
- Math.round((unloadInvHistory.uomValue || item.uomValue * item.actualPackQty) * 100) / 100
239
- }`,
240
- product_uom: `${unloadInvHistory.uom || item.uom}`,
241
- product_gross_weight: item.product.grossWeight || null,
242
- unit_price: item.unitPrice || null,
243
- expiry_date: unloadInvHistory.expiryDate && unloadInvHistory.expiryDate != '' ? new Date(unloadInvHistory.expiryDate).toISOString().split('T')[0] : '',
244
- manufacture_date: item.manufactureDate,
245
- reusable_pallet_id: unloadInvHistory.reusablePalletId || '',
246
- remark: (item.remark ? item.remark : '') + (item.issue ? ' [Issue]: ' + item.issue : '')
247
- }
248
- })
249
- )
250
- return acc
251
- }, [])
252
- .map((item, idx) => ({ list_no: idx + 1, ...item })),
326
+ pallet_list,
327
+ product_list,
253
328
  serialNumber: foundInventoryItem
254
329
  }
255
330
 
@@ -10,23 +10,29 @@ export const inventoriesByPalletResolver = {
10
10
  const { domain, user }: { domain: Domain; user: User } = context.state
11
11
  const params = { filters, pagination }
12
12
  try {
13
- const bizplaceId = params.filters.find(x => x.name == 'bizplace_id')
14
- const productFilters = params.filters.filter(x => x.name == 'productName')
15
- const inventoriesFilters = params.filters.find(x => x.name == 'invFilter')
16
- const recallFilters = params.filters.find(x => x.name === 'recall')
17
- const skipLockCheckFilters = params.filters.find(x => x.name === 'skipLockCheck')
18
- let skipLockCheck: Boolean =
19
- skipLockCheckFilters && skipLockCheckFilters?.value ? skipLockCheckFilters?.value : false
20
- const productFilterColumns = ['sku', 'brandSku', 'name', 'description', 'brand', 'subBrand']
21
- params.filters = params.filters.filter(x => x.name != 'productName' && x.name != 'invFilter')
22
- const batchIdFilters = params.filters.find(x => x.name == 'batchId')
23
-
24
- if (!params.filters.find((filter: any) => filter.name === 'bizplace_id')) {
25
- throw new Error('No Bizplace found')
26
- }
13
+ const bizplaceId = params.filters.find(x => x.name == 'bizplace_id')
14
+ const releaseShelfLifeOverrideFilter = params.filters.find(x => x.name == 'releaseShelfLifeOverride')
15
+ const releaseShelfLifeOverride =
16
+ releaseShelfLifeOverrideFilter && releaseShelfLifeOverrideFilter.value != null
17
+ ? Number(releaseShelfLifeOverrideFilter.value)
18
+ : null
19
+ params.filters = params.filters.filter(x => x.name != 'releaseShelfLifeOverride')
20
+ const productFilters = params.filters.filter(x => x.name == 'productName')
21
+ const inventoriesFilters = params.filters.find(x => x.name == 'invFilter')
22
+ const recallFilters = params.filters.find(x => x.name === 'recall')
23
+ const skipLockCheckFilters = params.filters.find(x => x.name === 'skipLockCheck')
24
+ let skipLockCheck: Boolean =
25
+ skipLockCheckFilters && skipLockCheckFilters?.value ? skipLockCheckFilters?.value : false
26
+ const productFilterColumns = ['sku', 'brandSku', 'name', 'description', 'brand', 'subBrand']
27
+ params.filters = params.filters.filter(x => x.name != 'productName' && x.name != 'invFilter')
28
+ const batchIdFilters = params.filters.find(x => x.name == 'batchId')
29
+
30
+ if (!params.filters.find((filter: any) => filter.name === 'bizplace_id')) {
31
+ throw new Error('No Bizplace found')
32
+ }
27
33
 
28
- const locationFilters = params.filters.find(x => x.name == 'locationName')
29
- params.filters = params.filters.filter(x => x.name != 'locationName')
34
+ const locationFilters = params.filters.find(x => x.name == 'locationName')
35
+ params.filters = params.filters.filter(x => x.name != 'locationName')
30
36
 
31
37
  const qb: SelectQueryBuilder<Inventory> = getRepository(Inventory).createQueryBuilder('iv')
32
38
  buildQuery(qb, params, context)
@@ -43,7 +49,7 @@ export const inventoriesByPalletResolver = {
43
49
  .andWhere('iv.qty > 0')
44
50
  .andWhere('iv.transfer_qty <= 0')
45
51
  .andWhere('iv.transfer_uom_value <= 0')
46
- .andWhere('iv.lock_inventory is not true')
52
+ .andWhere('iv.lock_inventory is not true')
47
53
  .andWhere(
48
54
  `location.type ${recallFilters?.value === true ? '' : 'NOT'} IN ('${LOCATION_TYPE.QUARANTINE}', '${
49
55
  LOCATION_TYPE.RESERVE
@@ -72,10 +78,8 @@ export const inventoriesByPalletResolver = {
72
78
  }
73
79
 
74
80
  if (inventoriesFilters?.value?.length > 0) {
75
-
76
81
  qb.andWhere('iv.id NOT IN (:...inventoriesIds)', {
77
82
  inventoriesIds: inventoriesFilters.value
78
-
79
83
  })
80
84
  }
81
85
 
@@ -132,12 +136,34 @@ export const inventoriesByPalletResolver = {
132
136
  qb.andWhere(`iv.batch_id ilike '${batchIdFilters.value}'`)
133
137
  }
134
138
  qb.andWhere(
135
- 'iv.obsolete = true and case when iv.expiration_date is not null and product.min_outbound_shelf_life is not null then CURRENT_DATE > iv.expiration_date - product.min_outbound_shelf_life else true end)'
139
+ `iv.obsolete = true AND iv.expiration_date IS NOT NULL AND (
140
+ CASE
141
+ WHEN :releaseOverride::integer IS NOT NULL AND :releaseOverride::integer > 0 THEN
142
+ CURRENT_DATE >= iv.expiration_date - :releaseOverride::integer
143
+ WHEN product.min_outbound_shelf_life IS NOT NULL AND product.min_outbound_shelf_life > 0 THEN
144
+ CURRENT_DATE >= iv.expiration_date - product.min_outbound_shelf_life
145
+ ELSE
146
+ FALSE
147
+ END
148
+ )`,
149
+ { releaseOverride: releaseShelfLifeOverride ?? null }
136
150
  )
137
151
  } else {
138
152
  qb.andWhere('iv.obsolete = false')
139
153
  qb.andWhere(
140
- 'case when iv.expiration_date is not null and product.min_outbound_shelf_life is not null then CURRENT_DATE < iv.expiration_date - product.min_outbound_shelf_life else true end'
154
+ `(
155
+ iv.expiration_date IS NULL
156
+ OR
157
+ CASE
158
+ WHEN :releaseOverride::integer IS NOT NULL AND :releaseOverride::integer > 0 THEN
159
+ CURRENT_DATE < iv.expiration_date - :releaseOverride::integer
160
+ WHEN product.min_outbound_shelf_life IS NOT NULL AND product.min_outbound_shelf_life > 0 THEN
161
+ CURRENT_DATE < iv.expiration_date - product.min_outbound_shelf_life
162
+ ELSE
163
+ TRUE
164
+ END
165
+ )`,
166
+ { releaseOverride: releaseShelfLifeOverride ?? null }
141
167
  )
142
168
  }
143
169
 
@@ -181,7 +207,7 @@ export const inventoriesByPalletResolver = {
181
207
  productBrand: item.product.brand,
182
208
  productId: item.product.id,
183
209
  productDetailId: item.productDetail.id,
184
- isInventoryDecimal: item.product.isInventoryDecimal,
210
+ isInventoryDecimal: item.product.isInventoryDecimal
185
211
  }
186
212
  })
187
213
  )
@@ -121,7 +121,8 @@ export async function inventoriesByStrategy(
121
121
  uom,
122
122
  pickingStrategy,
123
123
  locationSortingRules,
124
- warehouseName
124
+ warehouseName,
125
+ releaseShelfLifeOverride
125
126
  },
126
127
  domain: Domain,
127
128
  trxMgr: EntityManager
@@ -158,14 +159,26 @@ export async function inventoriesByStrategy(
158
159
  })
159
160
  .andWhere('"INV"."obsolete" = false')
160
161
  .andWhere(
161
- 'case when "INV"."expiration_date" is not null and "PROD"."min_outbound_shelf_life" is not null then CURRENT_DATE < "INV"."expiration_date" - "PROD"."min_outbound_shelf_life" else true end'
162
+ `(
163
+ "INV"."expiration_date" IS NULL
164
+ OR
165
+ CASE
166
+ WHEN :releaseShelfLifeOverride::integer IS NOT NULL AND :releaseShelfLifeOverride::integer > 0 THEN
167
+ CURRENT_DATE < ("INV"."expiration_date" - :releaseShelfLifeOverride::integer)
168
+ WHEN "PROD"."min_outbound_shelf_life" IS NOT NULL AND "PROD"."min_outbound_shelf_life" > 0 THEN
169
+ CURRENT_DATE < ("INV"."expiration_date" - "PROD"."min_outbound_shelf_life")
170
+ ELSE
171
+ TRUE
172
+ END
173
+ )`
162
174
  )
163
175
  .setParameters({
164
176
  domainId: domain.id,
165
177
  productId,
166
178
  packingType,
167
179
  packingSize,
168
- uom
180
+ uom,
181
+ releaseShelfLifeOverride: releaseShelfLifeOverride ?? null
169
182
  })
170
183
 
171
184
  if (batchId !== '') {