@things-factory/sales-base 4.3.701 → 4.3.723
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.
- package/dist-server/service/draft-release-good/draft-release-good-create.js +29 -5
- package/dist-server/service/draft-release-good/draft-release-good-create.js.map +1 -1
- package/dist-server/service/draft-release-good/draft-release-good-query.js +12 -1
- package/dist-server/service/draft-release-good/draft-release-good-query.js.map +1 -1
- package/dist-server/service/release-good/release-good-mutation.js +149 -10
- package/dist-server/service/release-good/release-good-mutation.js.map +1 -1
- package/dist-server/service/release-good/release-good-query.js +19 -1
- package/dist-server/service/release-good/release-good-query.js.map +1 -1
- package/dist-server/utils/inventory-util.js +70 -9
- package/dist-server/utils/inventory-util.js.map +1 -1
- package/package.json +17 -17
- package/server/service/draft-release-good/draft-release-good-create.ts +28 -3
- package/server/service/draft-release-good/draft-release-good-query.ts +13 -0
- package/server/service/release-good/release-good-mutation.ts +182 -4
- package/server/service/release-good/release-good-query.ts +21 -1
- package/server/utils/inventory-util.ts +71 -9
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
getOutletBizplace,
|
|
23
23
|
getPermittedBizplaces
|
|
24
24
|
} from '@things-factory/biz-base'
|
|
25
|
+
import { ContactPoint } from '@things-factory/biz-base'
|
|
25
26
|
import { GeoCountry } from '@things-factory/geography'
|
|
26
27
|
import { generateId } from '@things-factory/id-rule-base'
|
|
27
28
|
import { WebhookEventsEnum, webhookHandler } from '@things-factory/integration-base'
|
|
@@ -30,7 +31,7 @@ import { MarketplaceStore } from '@things-factory/integration-marketplace'
|
|
|
30
31
|
import { Sellercraft, SellercraftStatus } from '@things-factory/integration-sellercraft'
|
|
31
32
|
import { MarketplaceOrder } from '@things-factory/marketplace-base'
|
|
32
33
|
// import { sendNotification } from '@things-factory/notification'
|
|
33
|
-
import { ProductBundleSetting, ProductDetail } from '@things-factory/product-base'
|
|
34
|
+
import { Product, ProductBundleSetting, ProductDetail } from '@things-factory/product-base'
|
|
34
35
|
import { PartnerSetting, Setting } from '@things-factory/setting-base'
|
|
35
36
|
import { Domain } from '@things-factory/shell'
|
|
36
37
|
import { Inventory, ProductDetailStock } from '@things-factory/warehouse-base'
|
|
@@ -620,6 +621,159 @@ export async function generateReleaseGoodFunction(
|
|
|
620
621
|
await InventoryUtil.validateWarehousePartnersProductsQuantity(domain, bizplace, orderInventories, context, tx)
|
|
621
622
|
/** End Validate Release Order Product Quantity Section */
|
|
622
623
|
|
|
624
|
+
/**
|
|
625
|
+
* Enforce outbound shelf-life at submission time using deliverTo.releaseShelfLife (if present).
|
|
626
|
+
* This guards scenarios where user selected inventories first and then set Contact Point,
|
|
627
|
+
* ensuring final validation rejects items that don't meet the shelf-life requirement.
|
|
628
|
+
*/
|
|
629
|
+
let outboundShelfLifeOverride: number | null = null
|
|
630
|
+
let contactPoint: ContactPoint = null
|
|
631
|
+
const deliverToId: string | null =
|
|
632
|
+
typeof releaseGood?.deliverTo === 'string' && releaseGood?.deliverTo ? String(releaseGood.deliverTo) : null
|
|
633
|
+
if (deliverToId) {
|
|
634
|
+
contactPoint = await tx.getRepository(ContactPoint).findOne({ where: { id: deliverToId } })
|
|
635
|
+
if (contactPoint?.releaseShelfLife != null && contactPoint.releaseShelfLife !== 0) {
|
|
636
|
+
outboundShelfLifeOverride = Number(contactPoint.releaseShelfLife)
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (orderInventories?.length) {
|
|
641
|
+
// Helper function to build inventory availability query with optional warehouse and batch filters
|
|
642
|
+
const buildInventoryAvailabilityQuery = (
|
|
643
|
+
productId: string,
|
|
644
|
+
packingType: string,
|
|
645
|
+
packingSize: number,
|
|
646
|
+
uom: string,
|
|
647
|
+
warehouseName: string | null,
|
|
648
|
+
batchId: string | null,
|
|
649
|
+
outboundShelfLifeOverride: number | null
|
|
650
|
+
) => {
|
|
651
|
+
const query = `
|
|
652
|
+
select
|
|
653
|
+
coalesce(sum(
|
|
654
|
+
case when (iv.qty - greatest(coalesce(iv.locked_qty,0),0) - greatest(coalesce(pds.unassigned_qty,0),0)) < 0
|
|
655
|
+
then 0
|
|
656
|
+
else (iv.qty - greatest(coalesce(iv.locked_qty,0),0) - greatest(coalesce(pds.unassigned_qty,0),0))
|
|
657
|
+
end
|
|
658
|
+
), 0) as total_available_qty,
|
|
659
|
+
coalesce(sum(
|
|
660
|
+
case when (iv.uom_value - greatest(coalesce(iv.locked_uom_value,0),0) - greatest(coalesce(pds.unassigned_uom_value,0),0)) < 0
|
|
661
|
+
then 0
|
|
662
|
+
else (iv.uom_value - greatest(coalesce(iv.locked_uom_value,0),0) - greatest(coalesce(pds.unassigned_uom_value,0),0))
|
|
663
|
+
end
|
|
664
|
+
), 0) as total_available_uom_value,
|
|
665
|
+
max(p.sku) as product_sku
|
|
666
|
+
from inventories iv
|
|
667
|
+
left join product_detail_stocks pds on pds.product_detail_id = iv.product_detail_id
|
|
668
|
+
inner join products p on p.id = iv.product_id
|
|
669
|
+
inner join locations loc on loc.id = iv.location_id
|
|
670
|
+
inner join warehouses w on w.id = loc.warehouse_id
|
|
671
|
+
where iv.domain_id = $1
|
|
672
|
+
and iv.bizplace_id = $2
|
|
673
|
+
and iv.product_id = $3
|
|
674
|
+
and iv.packing_type = $4
|
|
675
|
+
and iv.packing_size = $5
|
|
676
|
+
and iv.uom = $6
|
|
677
|
+
and iv.status = 'STORED'
|
|
678
|
+
and loc.type not in ('QUARANTINE','RESERVE','DAMAGE')
|
|
679
|
+
and iv.obsolete = false
|
|
680
|
+
and (
|
|
681
|
+
iv.expiration_date is null
|
|
682
|
+
or
|
|
683
|
+
case
|
|
684
|
+
when $9::integer is not null and $9::integer > 0 then
|
|
685
|
+
CURRENT_DATE < iv.expiration_date - $9::integer
|
|
686
|
+
when p.min_outbound_shelf_life is not null and p.min_outbound_shelf_life > 0 then
|
|
687
|
+
CURRENT_DATE < iv.expiration_date - p.min_outbound_shelf_life
|
|
688
|
+
else
|
|
689
|
+
true
|
|
690
|
+
end
|
|
691
|
+
)
|
|
692
|
+
and ($7::text is null or w.name = $7::text)
|
|
693
|
+
and ($8::text is null or iv.batch_id = $8::text)
|
|
694
|
+
`
|
|
695
|
+
|
|
696
|
+
const params = [
|
|
697
|
+
domain.id,
|
|
698
|
+
bizplace.id,
|
|
699
|
+
productId,
|
|
700
|
+
packingType,
|
|
701
|
+
packingSize,
|
|
702
|
+
uom,
|
|
703
|
+
warehouseName,
|
|
704
|
+
batchId,
|
|
705
|
+
outboundShelfLifeOverride
|
|
706
|
+
]
|
|
707
|
+
|
|
708
|
+
return { query, params }
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const insufficientItems: any[] = []
|
|
712
|
+
for (const item of orderInventories) {
|
|
713
|
+
// Only validate standard (non-bundle) rows that have product/productDetail context
|
|
714
|
+
if (!item?.productDetail?.id || !item?.uom) continue
|
|
715
|
+
|
|
716
|
+
const productDetailId = item.productDetail.id
|
|
717
|
+
const productId = item?.product?.id || item?.productDetail?.product?.id
|
|
718
|
+
const packingType = item.packingType
|
|
719
|
+
const packingSize = item.packingSize
|
|
720
|
+
const uom = item.uom
|
|
721
|
+
const warehouseName = (item as any)?.warehouseCode ? String((item as any).warehouseCode).trim() : null
|
|
722
|
+
const batchId = item?.batchId && item.batchId !== '-' ? String(item.batchId).trim() : null
|
|
723
|
+
|
|
724
|
+
// Validate availability with shelf-life predicate applied
|
|
725
|
+
const { query, params } = buildInventoryAvailabilityQuery(
|
|
726
|
+
productId,
|
|
727
|
+
packingType,
|
|
728
|
+
packingSize,
|
|
729
|
+
uom,
|
|
730
|
+
warehouseName,
|
|
731
|
+
batchId,
|
|
732
|
+
outboundShelfLifeOverride
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
const rows = await tx.query(query, params)
|
|
736
|
+
|
|
737
|
+
const totalQty = parseFloat(rows?.[0]?.total_available_qty || '0')
|
|
738
|
+
// Normalize release quantity for non-decimal products
|
|
739
|
+
let reqQty = item.releaseQty
|
|
740
|
+
if ((item as any)?.product?.isInventoryDecimal === false) {
|
|
741
|
+
reqQty = Math.round(reqQty)
|
|
742
|
+
} else {
|
|
743
|
+
reqQty = Math.round(reqQty * 1000) / 1000
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
let productSku: string = rows?.[0]?.product_sku || null
|
|
747
|
+
if (!productSku && productId) {
|
|
748
|
+
const prod: Product = await tx
|
|
749
|
+
.getRepository(Product)
|
|
750
|
+
.findOne({ where: { id: productId }, select: ['id', 'sku'] })
|
|
751
|
+
if (prod?.sku) {
|
|
752
|
+
productSku = prod.sku
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
if (totalQty + 1e-6 < reqQty) {
|
|
756
|
+
insufficientItems.push({
|
|
757
|
+
productId,
|
|
758
|
+
productDetailId,
|
|
759
|
+
productSku: productSku,
|
|
760
|
+
packingType,
|
|
761
|
+
packingSize,
|
|
762
|
+
uom,
|
|
763
|
+
batchId,
|
|
764
|
+
requiredQty: reqQty,
|
|
765
|
+
availableQty: totalQty
|
|
766
|
+
})
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
if (insufficientItems.length > 0) {
|
|
770
|
+
throw new ValidationError({
|
|
771
|
+
...ValidationError.ERROR_CODES.INSUFFICIENT_STOCK,
|
|
772
|
+
detail: { data: JSON.stringify(insufficientItems) }
|
|
773
|
+
})
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
623
777
|
const orderSource: string = releaseGood.source
|
|
624
778
|
switch (orderSource) {
|
|
625
779
|
case ApplicationType.SELLERCRAFT:
|
|
@@ -801,6 +955,7 @@ export async function generateReleaseGoodFunction(
|
|
|
801
955
|
lastMileDelivery: lmd || null,
|
|
802
956
|
codOption: releaseGood?.codOption || null,
|
|
803
957
|
paidAmount: releaseGood?.paidAmount || null,
|
|
958
|
+
deliverTo: contactPoint || null,
|
|
804
959
|
creator: user,
|
|
805
960
|
updater: user
|
|
806
961
|
}
|
|
@@ -948,7 +1103,13 @@ export async function generateReleaseGoodFunction(
|
|
|
948
1103
|
bizplace,
|
|
949
1104
|
warehouseDomain,
|
|
950
1105
|
tx,
|
|
951
|
-
newOrderInv.batchId
|
|
1106
|
+
newOrderInv.batchId,
|
|
1107
|
+
undefined,
|
|
1108
|
+
undefined,
|
|
1109
|
+
undefined,
|
|
1110
|
+
undefined,
|
|
1111
|
+
undefined,
|
|
1112
|
+
outboundShelfLifeOverride
|
|
952
1113
|
)
|
|
953
1114
|
|
|
954
1115
|
assignedOrderInventories = assignedOrderInventories.map(aoi => {
|
|
@@ -1681,6 +1842,21 @@ export async function bulkGenerateReleaseGood(
|
|
|
1681
1842
|
newReleaseGood.shippingOrder = createdSO
|
|
1682
1843
|
}
|
|
1683
1844
|
|
|
1845
|
+
/**
|
|
1846
|
+
* Enforce outbound shelf-life at submission time using deliverTo.releaseShelfLife (if present).
|
|
1847
|
+
* This guards scenarios where user selected inventories first and then set Contact Point,
|
|
1848
|
+
* ensuring final validation rejects items that don't meet the shelf-life requirement.
|
|
1849
|
+
*/
|
|
1850
|
+
let outboundShelfLifeOverride: number | null = null
|
|
1851
|
+
const deliverToId: string | null =
|
|
1852
|
+
typeof releaseGood?.deliverTo === 'string' && releaseGood?.deliverTo ? String(releaseGood.deliverTo) : null
|
|
1853
|
+
if (deliverToId) {
|
|
1854
|
+
const contactPoint: ContactPoint = await tx.getRepository(ContactPoint).findOne({ where: { id: deliverToId } })
|
|
1855
|
+
if (contactPoint?.releaseShelfLife != null && contactPoint.releaseShelfLife !== 0) {
|
|
1856
|
+
outboundShelfLifeOverride = Number(contactPoint.releaseShelfLife)
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1684
1860
|
let finalOrderInventories: Partial<OrderInventory>[] = []
|
|
1685
1861
|
|
|
1686
1862
|
// pre assign inventory to orderInventories
|
|
@@ -1717,7 +1893,9 @@ export async function bulkGenerateReleaseGood(
|
|
|
1717
1893
|
'',
|
|
1718
1894
|
null,
|
|
1719
1895
|
oi.cartonId,
|
|
1720
|
-
oi.expirationDate
|
|
1896
|
+
oi.expirationDate,
|
|
1897
|
+
undefined,
|
|
1898
|
+
outboundShelfLifeOverride
|
|
1721
1899
|
)
|
|
1722
1900
|
|
|
1723
1901
|
finalOrderInventories.push(
|
|
@@ -1976,7 +2154,7 @@ function extractRawReleaseGoods(rawReleaseGoods): Partial<ReleaseGood[]> {
|
|
|
1976
2154
|
packingSize: item.packingSize,
|
|
1977
2155
|
uom: item.uom,
|
|
1978
2156
|
releaseQty: Math.round(parseFloat(item.releaseQty) * 1000) / 1000,
|
|
1979
|
-
releaseUomValue: Math.round(parseFloat(item.releaseUomValue) * 1000) / 1000
|
|
2157
|
+
releaseUomValue: Math.round(parseFloat(item.releaseUomValue) * 1000) / 1000
|
|
1980
2158
|
})
|
|
1981
2159
|
}
|
|
1982
2160
|
} else {
|
|
@@ -952,6 +952,13 @@ export async function bulkReleaseGoodsAvailableItemsFunction(
|
|
|
952
952
|
const companyBizplaceId: Bizplace = await getCompanyBizplace(null, null, bizplaceId)
|
|
953
953
|
|
|
954
954
|
if (!rawReleaseGoods) return
|
|
955
|
+
// derive optional release shelf life override (use the first non-null value if provided)
|
|
956
|
+
const releaseShelfLifeOverride: number | null = (() => {
|
|
957
|
+
const found = (rawReleaseGoods || []).find(
|
|
958
|
+
(r: any) => r?.releaseShelfLifeOverride !== undefined && r?.releaseShelfLifeOverride !== null
|
|
959
|
+
)
|
|
960
|
+
return found ? Number(found.releaseShelfLifeOverride) : null
|
|
961
|
+
})()
|
|
955
962
|
// derive optional filters
|
|
956
963
|
const uniqueWarehouseCodes: string[] = Array.from(
|
|
957
964
|
new Set(
|
|
@@ -1067,6 +1074,18 @@ export async function bulkReleaseGoodsAvailableItemsFunction(
|
|
|
1067
1074
|
AND i.transfer_uom_value <= 0
|
|
1068
1075
|
AND ( $5::text IS NULL OR w.name = $5 )
|
|
1069
1076
|
${`AND ( $6::text IS NULL OR i.batch_id = $6 )`}
|
|
1077
|
+
AND (
|
|
1078
|
+
i.expiration_date IS NULL
|
|
1079
|
+
OR
|
|
1080
|
+
CASE
|
|
1081
|
+
WHEN $7::integer IS NOT NULL AND $7::integer > 0 THEN
|
|
1082
|
+
CURRENT_DATE < (i.expiration_date - $7::integer)
|
|
1083
|
+
WHEN p.min_outbound_shelf_life IS NOT NULL AND p.min_outbound_shelf_life > 0 THEN
|
|
1084
|
+
CURRENT_DATE < (i.expiration_date - p.min_outbound_shelf_life)
|
|
1085
|
+
ELSE
|
|
1086
|
+
TRUE
|
|
1087
|
+
END
|
|
1088
|
+
)
|
|
1070
1089
|
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${
|
|
1071
1090
|
groupByWarehouse ? ', w.name' : ''
|
|
1072
1091
|
}
|
|
@@ -1080,7 +1099,8 @@ export async function bulkReleaseGoodsAvailableItemsFunction(
|
|
|
1080
1099
|
LOCATION_TYPE.QUARANTINE,
|
|
1081
1100
|
LOCATION_TYPE.RESERVE,
|
|
1082
1101
|
uniqueWarehouseCodes.length === 1 ? uniqueWarehouseCodes[0] : null,
|
|
1083
|
-
uniqueBatchIds.length === 1 ? uniqueBatchIds[0] : null
|
|
1102
|
+
uniqueBatchIds.length === 1 ? uniqueBatchIds[0] : null,
|
|
1103
|
+
releaseShelfLifeOverride
|
|
1084
1104
|
]
|
|
1085
1105
|
)
|
|
1086
1106
|
|
|
@@ -78,6 +78,8 @@ export const InventoryUtil = {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
// optional override for outbound shelf life (from ContactPoint deliverTo)
|
|
82
|
+
const releaseShelfLifeOverrideFilter = filters.find(f => f.name === 'releaseShelfLifeOverride')
|
|
81
83
|
let queryStrings = `
|
|
82
84
|
CREATE TEMP TABLE temp_inventory_product_group ON COMMIT DROP AS (
|
|
83
85
|
SELECT * FROM (
|
|
@@ -163,7 +165,18 @@ export const InventoryUtil = {
|
|
|
163
165
|
) bp on i.product_id = bp.product_id
|
|
164
166
|
WHERE i.bizplace_id IN (${bizplaceIds})
|
|
165
167
|
AND i.lock_inventory is not true
|
|
166
|
-
AND
|
|
168
|
+
AND (
|
|
169
|
+
i.expiration_date IS NULL
|
|
170
|
+
OR
|
|
171
|
+
CASE
|
|
172
|
+
WHEN $2::integer IS NOT NULL AND $2::integer > 0 THEN
|
|
173
|
+
CURRENT_DATE < (i.expiration_date - $2::integer)
|
|
174
|
+
WHEN p.min_outbound_shelf_life IS NOT NULL AND p.min_outbound_shelf_life > 0 THEN
|
|
175
|
+
CURRENT_DATE < (i.expiration_date - p.min_outbound_shelf_life)
|
|
176
|
+
ELSE
|
|
177
|
+
TRUE
|
|
178
|
+
END
|
|
179
|
+
)
|
|
167
180
|
${productDetailWhereClause}
|
|
168
181
|
${
|
|
169
182
|
productFilter
|
|
@@ -252,7 +265,10 @@ export const InventoryUtil = {
|
|
|
252
265
|
)
|
|
253
266
|
`
|
|
254
267
|
|
|
255
|
-
await trxMgr.query(queryStrings, [
|
|
268
|
+
await trxMgr.query(queryStrings, [
|
|
269
|
+
domain.id,
|
|
270
|
+
releaseShelfLifeOverrideFilter ? releaseShelfLifeOverrideFilter.value : null
|
|
271
|
+
])
|
|
256
272
|
|
|
257
273
|
const [{ total }]: any = await trxMgr.query(`select count(*) as total from temp_inventory_product_group`)
|
|
258
274
|
let items: any[] = []
|
|
@@ -295,6 +311,7 @@ export const InventoryUtil = {
|
|
|
295
311
|
let cycleCountFilter = filters.find(filter => filter.name == 'cycleCount')?.value
|
|
296
312
|
|
|
297
313
|
const _groupType = filters.find(res => res.name == 'groupType')
|
|
314
|
+
const releaseShelfLifeOverrideFilter = filters.find(filter => filter.name === 'releaseShelfLifeOverride')
|
|
298
315
|
let queryStrings = `
|
|
299
316
|
CREATE TEMP TABLE temp_inventory_product_group AS (
|
|
300
317
|
SELECT * FROM (
|
|
@@ -335,7 +352,18 @@ export const InventoryUtil = {
|
|
|
335
352
|
AND i.transfer_qty <= 0
|
|
336
353
|
AND i.transfer_uom_value <= 0
|
|
337
354
|
AND i.lock_inventory is not true
|
|
338
|
-
AND
|
|
355
|
+
AND (
|
|
356
|
+
i.expiration_date IS NULL
|
|
357
|
+
OR
|
|
358
|
+
CASE
|
|
359
|
+
WHEN $2::integer IS NOT NULL AND $2::integer > 0 THEN
|
|
360
|
+
CURRENT_DATE < i.expiration_date - $2::integer
|
|
361
|
+
WHEN p.min_outbound_shelf_life IS NOT NULL AND p.min_outbound_shelf_life > 0 THEN
|
|
362
|
+
CURRENT_DATE < i.expiration_date - p.min_outbound_shelf_life
|
|
363
|
+
ELSE
|
|
364
|
+
TRUE
|
|
365
|
+
END
|
|
366
|
+
)
|
|
339
367
|
${
|
|
340
368
|
cycleCountFilter
|
|
341
369
|
? `AND i.id NOT IN (
|
|
@@ -425,7 +453,10 @@ export const InventoryUtil = {
|
|
|
425
453
|
})
|
|
426
454
|
}
|
|
427
455
|
|
|
428
|
-
await trxMgr.query(queryStrings, [
|
|
456
|
+
await trxMgr.query(queryStrings, [
|
|
457
|
+
domain.id,
|
|
458
|
+
releaseShelfLifeOverrideFilter ? releaseShelfLifeOverrideFilter.value : null
|
|
459
|
+
])
|
|
429
460
|
|
|
430
461
|
const [{ total }]: any = await trxMgr.query(
|
|
431
462
|
`select count(*) as total from temp_inventory_product_group ${filterGroupTypeQuery ? filterGroupTypeQuery : ''}`
|
|
@@ -512,7 +543,16 @@ export const InventoryUtil = {
|
|
|
512
543
|
AND i.transfer_qty <= 0
|
|
513
544
|
AND i.transfer_uom_value <= 0
|
|
514
545
|
AND i.lock_inventory is not true
|
|
515
|
-
AND
|
|
546
|
+
AND (
|
|
547
|
+
i.expiration_date IS NULL
|
|
548
|
+
OR
|
|
549
|
+
CASE
|
|
550
|
+
WHEN p.min_outbound_shelf_life IS NOT NULL AND p.min_outbound_shelf_life > 0 THEN
|
|
551
|
+
CURRENT_DATE < i.expiration_date - p.min_outbound_shelf_life
|
|
552
|
+
ELSE
|
|
553
|
+
TRUE
|
|
554
|
+
END
|
|
555
|
+
)
|
|
516
556
|
${productWhereClause}
|
|
517
557
|
GROUP BY
|
|
518
558
|
i.product_detail_id,
|
|
@@ -716,7 +756,16 @@ export const InventoryUtil = {
|
|
|
716
756
|
WHERE i.domain_id = $1
|
|
717
757
|
AND l2.type NOT IN ('${LOCATION_TYPE.QUARANTINE}', '${LOCATION_TYPE.RESERVE}')
|
|
718
758
|
AND i.obsolete = false
|
|
719
|
-
AND
|
|
759
|
+
AND (
|
|
760
|
+
i.expiration_date IS NULL
|
|
761
|
+
OR
|
|
762
|
+
CASE
|
|
763
|
+
WHEN p.min_outbound_shelf_life IS NOT NULL AND p.min_outbound_shelf_life > 0 THEN
|
|
764
|
+
CURRENT_DATE < i.expiration_date - p.min_outbound_shelf_life
|
|
765
|
+
ELSE
|
|
766
|
+
TRUE
|
|
767
|
+
END
|
|
768
|
+
)
|
|
720
769
|
${apiWhereClause}
|
|
721
770
|
GROUP BY
|
|
722
771
|
p.id,
|
|
@@ -873,7 +922,8 @@ export const InventoryUtil = {
|
|
|
873
922
|
recall: boolean = null,
|
|
874
923
|
cartonId?: string,
|
|
875
924
|
expirationDate?: Date,
|
|
876
|
-
warehouseName?: string
|
|
925
|
+
warehouseName?: string,
|
|
926
|
+
outboundShelfLifeOverride?: number
|
|
877
927
|
): Promise<OrderInventory[]> {
|
|
878
928
|
let strictProduct = 'false'
|
|
879
929
|
|
|
@@ -918,14 +968,26 @@ export const InventoryUtil = {
|
|
|
918
968
|
.andWhere('"iv"."transfer_uom_value" <= 0')
|
|
919
969
|
.andWhere('"iv"."product_id" = :productId')
|
|
920
970
|
.andWhere(
|
|
921
|
-
|
|
971
|
+
`(
|
|
972
|
+
"iv"."expiration_date" IS NULL
|
|
973
|
+
OR
|
|
974
|
+
CASE
|
|
975
|
+
WHEN :outboundShelfLifeOverride::integer IS NOT NULL AND :outboundShelfLifeOverride::integer > 0 THEN
|
|
976
|
+
CURRENT_DATE < ("iv"."expiration_date" - :outboundShelfLifeOverride::integer)
|
|
977
|
+
WHEN "p"."min_outbound_shelf_life" IS NOT NULL AND "p"."min_outbound_shelf_life" > 0 THEN
|
|
978
|
+
CURRENT_DATE < ("iv"."expiration_date" - "p"."min_outbound_shelf_life")
|
|
979
|
+
ELSE
|
|
980
|
+
TRUE
|
|
981
|
+
END
|
|
982
|
+
)`
|
|
922
983
|
)
|
|
923
984
|
.setParameters({
|
|
924
985
|
domainId: domain.id,
|
|
925
986
|
bizplaceId: customerBizplace.id,
|
|
926
987
|
productId: product.id,
|
|
927
988
|
status: INVENTORY_STATUS.STORED,
|
|
928
|
-
locationTypes
|
|
989
|
+
locationTypes,
|
|
990
|
+
outboundShelfLifeOverride: outboundShelfLifeOverride ?? null
|
|
929
991
|
})
|
|
930
992
|
|
|
931
993
|
if (batchId) {
|