@things-factory/operato-hub 4.3.698 → 4.3.700

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.
@@ -44,6 +44,22 @@ api_1.restfulApiRouter.post(`/${apiVersion}/warehouse/add-release-order`, middle
44
44
  where: { id: customerBizplaceId },
45
45
  relations: ['domain']
46
46
  });
47
+ // optional override: release shelf life from ContactPoint
48
+ const contactPointId = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.contactPointId;
49
+ let releaseShelfLifeOverride = null;
50
+ let deliverToContactPoint = null;
51
+ if (contactPointId) {
52
+ const contactPoint = await tx
53
+ .getRepository(biz_base_1.ContactPoint)
54
+ .findOne({ where: { id: contactPointId } });
55
+ if (!contactPoint) {
56
+ throw new error_util_1.ApiError('E04', 'contactPoint not found');
57
+ }
58
+ if ((contactPoint === null || contactPoint === void 0 ? void 0 : contactPoint.releaseShelfLife) != null && contactPoint.releaseShelfLife !== 0) {
59
+ releaseShelfLifeOverride = contactPoint.releaseShelfLife;
60
+ }
61
+ deliverToContactPoint = contactPoint;
62
+ }
47
63
  const worksheetPickingAssignment = await tx.getRepository(setting_base_1.Setting).findOne({
48
64
  where: { domain, category: 'id-rule', name: 'enable-worksheet-picking-activation-assignment' }
49
65
  });
@@ -62,7 +78,12 @@ api_1.restfulApiRouter.post(`/${apiVersion}/warehouse/add-release-order`, middle
62
78
  salesType: ((_e = bodyReq.xilnexSetting) === null || _e === void 0 ? void 0 : _e.salesType) || ''
63
79
  });
64
80
  }
65
- let newPhone1 = ((_f = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _f === void 0 ? void 0 : _f.phone1) ? bodyReq.deliverTo.phone1.replace(/\D/g, '') : null;
81
+ // derive phone values: prefer contact point when provided, else use body
82
+ let newPhone1 = (deliverToContactPoint === null || deliverToContactPoint === void 0 ? void 0 : deliverToContactPoint.phone)
83
+ ? String(deliverToContactPoint.phone).replace(/\D/g, '')
84
+ : ((_f = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _f === void 0 ? void 0 : _f.phone1)
85
+ ? bodyReq.deliverTo.phone1.replace(/\D/g, '')
86
+ : null;
66
87
  let newPhone2 = ((_g = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _g === void 0 ? void 0 : _g.phone2) ? bodyReq.deliverTo.phone2.replace(/\D/g, '') : null;
67
88
  releaseGood = {
68
89
  domain,
@@ -82,23 +103,36 @@ api_1.restfulApiRouter.post(`/${apiVersion}/warehouse/add-release-order`, middle
82
103
  type: bodyReq.type,
83
104
  marketplaceOrderStatus: bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.marketplaceOrderStatus,
84
105
  remark: (bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.remark) || null,
85
- billingAddress: ((_h = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.billTo) === null || _h === void 0 ? void 0 : _h.billingAddress) || null,
86
- deliveryAddress1: ((_j = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _j === void 0 ? void 0 : _j.deliveryAddress1) || null,
87
- deliveryAddress2: ((_k = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _k === void 0 ? void 0 : _k.deliveryAddress2) || null,
106
+ // when contactPoint exists, override target fields from contactPoint, else use request body
107
+ billingAddress: deliverToContactPoint
108
+ ? deliverToContactPoint.billingAddress || null
109
+ : ((_h = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.billTo) === null || _h === void 0 ? void 0 : _h.billingAddress) || null,
110
+ deliveryAddress1: deliverToContactPoint
111
+ ? deliverToContactPoint.address || null
112
+ : ((_j = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _j === void 0 ? void 0 : _j.deliveryAddress1) || null,
113
+ deliveryAddress2: deliverToContactPoint
114
+ ? deliverToContactPoint.address2 || null
115
+ : ((_k = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _k === void 0 ? void 0 : _k.deliveryAddress2) || null,
88
116
  deliveryAddress3: ((_l = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _l === void 0 ? void 0 : _l.deliveryAddress3) || null,
89
117
  deliveryAddress4: ((_m = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _m === void 0 ? void 0 : _m.deliveryAddress4) || null,
90
118
  deliveryAddress5: ((_o = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _o === void 0 ? void 0 : _o.deliveryAddress5) || null,
91
- attentionTo: ((_p = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _p === void 0 ? void 0 : _p.attentionTo) || null,
92
- attentionCompany: ((_q = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _q === void 0 ? void 0 : _q.attentionCompany) || null,
93
- city: ((_r = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _r === void 0 ? void 0 : _r.city) || null,
119
+ attentionTo: deliverToContactPoint
120
+ ? deliverToContactPoint.name || null
121
+ : ((_p = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _p === void 0 ? void 0 : _p.attentionTo) || null,
122
+ attentionCompany: deliverToContactPoint
123
+ ? deliverToContactPoint.companyName || null
124
+ : ((_q = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _q === void 0 ? void 0 : _q.attentionCompany) || null,
125
+ city: deliverToContactPoint ? deliverToContactPoint.city || null : ((_r = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _r === void 0 ? void 0 : _r.city) || null,
94
126
  ward: (_s = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _s === void 0 ? void 0 : _s.ward,
95
- district: (_t = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _t === void 0 ? void 0 : _t.district,
96
- state: ((_u = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _u === void 0 ? void 0 : _u.state) || null,
97
- postalCode: ((_v = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _v === void 0 ? void 0 : _v.postalCode) || null,
98
- country: ((_w = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _w === void 0 ? void 0 : _w.country) || null,
127
+ district: deliverToContactPoint ? null : (_t = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _t === void 0 ? void 0 : _t.district,
128
+ state: deliverToContactPoint ? deliverToContactPoint.state || null : ((_u = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _u === void 0 ? void 0 : _u.state) || null,
129
+ postalCode: deliverToContactPoint
130
+ ? deliverToContactPoint.postCode || null
131
+ : ((_v = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _v === void 0 ? void 0 : _v.postalCode) || null,
132
+ country: deliverToContactPoint ? null : ((_w = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _w === void 0 ? void 0 : _w.country) || null,
99
133
  phone1: newPhone1,
100
134
  phone2: newPhone2,
101
- email: ((_x = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _x === void 0 ? void 0 : _x.email) || null,
135
+ email: deliverToContactPoint ? deliverToContactPoint.email || null : ((_x = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _x === void 0 ? void 0 : _x.email) || null,
102
136
  transporter: bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.transporter,
103
137
  trackingNo: bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.trackingNo,
104
138
  airwayBill: bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.airwayBill,
@@ -118,6 +152,7 @@ api_1.restfulApiRouter.post(`/${apiVersion}/warehouse/add-release-order`, middle
118
152
  shippingFee: bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.shippingFee,
119
153
  lmdOption: bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.lmdOption,
120
154
  priorityDelivery: bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.priorityDelivery,
155
+ deliverTo: deliverToContactPoint || null,
121
156
  shippingOrder: (bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.shippingOrder)
122
157
  ? {
123
158
  shipName: bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.shippingOrder.shipName,
@@ -169,204 +204,149 @@ api_1.restfulApiRouter.post(`/${apiVersion}/warehouse/add-release-order`, middle
169
204
  // massage data
170
205
  massagedData = await massageOrderItems(releaseGood, bodyReq.orderInventories, context); //double check this function
171
206
  const invWithoutBatchId = batchIdStates.withoutBatchId;
207
+ // Helper function to build inventory availability query
208
+ const buildInventoryAvailabilityQuery = (item, domain, customerBizplace, options) => {
209
+ const { batchId, warehouseName, releaseShelfLifeOverride, requireWarehouseJoin, includeLockInventoryCheck } = options;
210
+ const hasWarehouse = warehouseName != null;
211
+ const hasBatch = batchId != null;
212
+ // Determine warehouse join strategy
213
+ const warehouseJoin = requireWarehouseJoin || hasWarehouse ? 'inner join warehouses w on w.id = loc.warehouse_id' : '';
214
+ // Build warehouse filter
215
+ let warehouseFilter = '';
216
+ if (requireWarehouseJoin) {
217
+ // For batch queries: always join warehouses, filter optionally
218
+ warehouseFilter = 'and ($8::text is null or w.name = $8::text)';
219
+ }
220
+ else if (hasWarehouse) {
221
+ // For non-batch queries: only filter if warehouse is specified
222
+ warehouseFilter = 'and w.name = $7';
223
+ }
224
+ // Build batch filter
225
+ const batchFilter = hasBatch ? 'and iv.batch_id = $7' : '';
226
+ // Calculate parameter indices
227
+ const shelfLifeParamIndex = hasBatch ? '$9' : hasWarehouse ? '$8' : '$7';
228
+ // Build lock inventory check
229
+ const lockInventoryCheck = includeLockInventoryCheck
230
+ ? `and not exists (
231
+ select 1 from inventories i
232
+ where i.domain_id = $1 and i.bizplace_id = $2 and i.product_id = $3 and i.packing_type = $4 and i.packing_size = $5 and i.uom = $6 and i.status = 'STORED' and i.lock_inventory is true
233
+ )`
234
+ : '';
235
+ const query = `
236
+ select
237
+ coalesce(sum(
238
+ case when (iv.qty - greatest(coalesce(iv.locked_qty,0),0) - greatest(coalesce(pds.unassigned_qty,0),0)) < 0
239
+ then 0
240
+ else (iv.qty - greatest(coalesce(iv.locked_qty,0),0) - greatest(coalesce(pds.unassigned_qty,0),0))
241
+ end
242
+ ), 0) as total_available_qty,
243
+ coalesce(sum(
244
+ case when (iv.uom_value - greatest(coalesce(iv.locked_uom_value,0),0) - greatest(coalesce(pds.unassigned_uom_value,0),0)) < 0
245
+ then 0
246
+ else (iv.uom_value - greatest(coalesce(iv.locked_uom_value,0),0) - greatest(coalesce(pds.unassigned_uom_value,0),0))
247
+ end
248
+ ), 0) as total_available_uom_value
249
+ from inventories iv
250
+ left join product_detail_stocks pds on pds.product_detail_id = iv.product_detail_id
251
+ inner join locations loc on loc.id = iv.location_id
252
+ ${warehouseJoin}
253
+ inner join products p on p.id = iv.product_id
254
+ where iv.domain_id = $1
255
+ and iv.bizplace_id = $2
256
+ and iv.product_id = $3
257
+ and iv.packing_type = $4
258
+ and iv.packing_size = $5
259
+ and iv.uom = $6
260
+ and iv.status = 'STORED'
261
+ and loc.type not in ('QUARANTINE','RESERVE','DAMAGE','STORAGE')
262
+ and iv.obsolete = false
263
+ and (case
264
+ when iv.expiration_date is not null
265
+ then CURRENT_DATE < iv.expiration_date - (
266
+ case
267
+ when ${shelfLifeParamIndex}::integer is not null and ${shelfLifeParamIndex}::integer > 0
268
+ then ${shelfLifeParamIndex}::integer
269
+ when p.min_outbound_shelf_life is not null
270
+ then p.min_outbound_shelf_life
271
+ else 0
272
+ end
273
+ )
274
+ else true
275
+ end)
276
+ ${batchFilter}
277
+ ${warehouseFilter}
278
+ ${lockInventoryCheck}
279
+ `;
280
+ // Build parameters array
281
+ const params = [
282
+ domain.id,
283
+ customerBizplace.id,
284
+ item.product.id,
285
+ item.packingType,
286
+ item.packingSize,
287
+ item.uom
288
+ ];
289
+ if (hasBatch) {
290
+ params.push(batchId);
291
+ }
292
+ if (requireWarehouseJoin || hasWarehouse) {
293
+ params.push(warehouseName);
294
+ }
295
+ params.push(releaseShelfLifeOverride);
296
+ return { query, params };
297
+ };
298
+ // Helper function to validate inventory availability
299
+ const validateInventoryAvailability = async (item, query, params, tx) => {
300
+ var _a, _b;
301
+ const rows = await tx.query(query, params);
302
+ const totalQty = parseFloat(((_a = rows === null || rows === void 0 ? void 0 : rows[0]) === null || _a === void 0 ? void 0 : _a.total_available_qty) || '0');
303
+ const totalUomVal = parseFloat(((_b = rows === null || rows === void 0 ? void 0 : rows[0]) === null || _b === void 0 ? void 0 : _b.total_available_uom_value) || '0');
304
+ let reqQty = item.releaseQty;
305
+ let reqUomVal = item.releaseUomValue;
306
+ if (!item.product.isInventoryDecimal) {
307
+ reqQty = Math.round(reqQty);
308
+ }
309
+ else {
310
+ reqQty = Math.round(reqQty * 1000) / 1000;
311
+ }
312
+ if (totalQty + 1e-6 < reqQty || totalUomVal + 1e-6 < reqUomVal) {
313
+ throw new error_util_1.ApiError('E01', 'INSUFFICIENT_STOCK');
314
+ }
315
+ };
172
316
  //inv without batchId will check stock in wboi
173
317
  if (invWithoutBatchId) {
174
318
  // validation
175
319
  await Promise.all(massagedData.combinedItems.map(async (item) => {
176
- var _a, _b;
177
- const hasWarehouse = !!item.warehouseCode;
178
- if (hasWarehouse) {
179
- // Validate availability within the specific warehouse across pickable locations (exclude Q/R/D/STORAGE)
180
- const rows = await tx.query(`
181
- select
182
- coalesce(sum(
183
- case when (iv.qty - greatest(coalesce(iv.locked_qty,0),0) - greatest(coalesce(pds.unassigned_qty,0),0)) < 0
184
- then 0
185
- else (iv.qty - greatest(coalesce(iv.locked_qty,0),0) - greatest(coalesce(pds.unassigned_qty,0),0))
186
- end
187
- ), 0) as total_available_qty,
188
- coalesce(sum(
189
- case when (iv.uom_value - greatest(coalesce(iv.locked_uom_value,0),0) - greatest(coalesce(pds.unassigned_uom_value,0),0)) < 0
190
- then 0
191
- else (iv.uom_value - greatest(coalesce(iv.locked_uom_value,0),0) - greatest(coalesce(pds.unassigned_uom_value,0),0))
192
- end
193
- ), 0) as total_available_uom_value
194
- from inventories iv
195
- left join product_detail_stocks pds on pds.product_detail_id = iv.product_detail_id
196
- inner join locations loc on loc.id = iv.location_id
197
- inner join warehouses w on w.id = loc.warehouse_id
198
- inner join products p on p.id = iv.product_id
199
- where iv.domain_id = $1
200
- and iv.bizplace_id = $2
201
- and iv.product_id = $3
202
- and iv.packing_type = $4
203
- and iv.packing_size = $5
204
- and iv.uom = $6
205
- and iv.status = 'STORED'
206
- and loc.type not in ('QUARANTINE','RESERVE','DAMAGE','STORAGE')
207
- and iv.obsolete = false
208
- and (case when iv.expiration_date is not null and p.min_outbound_shelf_life is not null then CURRENT_DATE < iv.expiration_date - p.min_outbound_shelf_life else true end)
209
- and w.name = $7
210
- and not exists (
211
- select 1 from inventories i
212
- where i.domain_id = $1 and i.bizplace_id = $2 and i.product_id = $3 and i.packing_type = $4 and i.packing_size = $5 and i.uom = $6 and i.status = 'STORED' and i.lock_inventory is true
213
- )
214
- `, [
215
- domain.id,
216
- customerBizplace.id,
217
- item.product.id,
218
- item.packingType,
219
- item.packingSize,
220
- item.uom,
221
- String(item.warehouseCode).trim()
222
- ]);
223
- const totalQty = parseFloat(((_a = rows === null || rows === void 0 ? void 0 : rows[0]) === null || _a === void 0 ? void 0 : _a.total_available_qty) || '0');
224
- const totalUomVal = parseFloat(((_b = rows === null || rows === void 0 ? void 0 : rows[0]) === null || _b === void 0 ? void 0 : _b.total_available_uom_value) || '0');
225
- let reqQty = item.releaseQty;
226
- let reqUomVal = item.releaseUomValue;
227
- if (!item.product.isInventoryDecimal) {
228
- reqQty = Math.round(reqQty);
229
- }
230
- else {
231
- reqQty = Math.round(reqQty * 1000) / 1000;
232
- }
233
- if (totalQty + 1e-6 < reqQty || totalUomVal + 1e-6 < reqUomVal) {
234
- throw new error_util_1.ApiError('E01', 'INSUFFICIENT_STOCK');
235
- }
236
- }
237
- else {
238
- // Fallback to existing global view-based validation when no warehouseCode provided
239
- let itemList = await tx.query(`
240
- select wboi.*
241
- from warehouse_bizplace_onhand_inventories wboi
242
- left join (
243
- select i2.product_id, i2.domain_id, i2.bizplace_id, i2.packing_type, i2.packing_size, i2.uom,
244
- sum(i2.qty) as storage_qty,
245
- sum(i2.uom_value) as storage_uom_value
246
- from inventories i2
247
- inner join locations l2 on l2.id = i2.location_id
248
- inner join warehouses w on w.id = l2.warehouse_id
249
- where i2.domain_id = $1
250
- and i2.bizplace_id = $2
251
- and i2.product_id = $3
252
- and i2.packing_type = $4
253
- and i2.packing_size = $5
254
- and i2.uom = $6
255
- and i2.status = 'STORED'
256
- and l2.type = 'STORAGE'
257
- group by i2.product_id, i2.domain_id, i2.bizplace_id, i2.packing_type, i2.packing_size, i2.uom
258
- ) storageInv
259
- on storageInv.product_id = wboi.product_id
260
- and storageInv.domain_id = wboi.domain_id
261
- and storageInv.bizplace_id = wboi.bizplace_id
262
- and storageInv.packing_type = wboi.packing_type
263
- and storageInv.packing_size = wboi.packing_size
264
- and storageInv.uom = wboi.uom
265
- left join (
266
- select i.product_id, i.domain_id, i.bizplace_id, i.packing_type, i.packing_size, i.uom, i.lock_inventory
267
- from inventories i
268
- where i.domain_id = $1
269
- and i.bizplace_id = $2
270
- and i.product_id = $3
271
- and i.packing_type = $4
272
- and i.packing_size = $5
273
- and i.uom = $6
274
- and i.status = 'STORED'
275
- group by i.product_id, i.domain_id, i.bizplace_id, i.packing_type, i.packing_size, i.uom, i.lock_inventory
276
- ) lockInv
277
- on lockInv.product_id = wboi.product_id
278
- and lockInv.domain_id = wboi.domain_id
279
- and lockInv.bizplace_id = wboi.bizplace_id
280
- and lockInv.packing_type = wboi.packing_type
281
- and lockInv.packing_size = wboi.packing_size
282
- and lockInv.uom = wboi.uom
283
- where wboi.domain_id = $1
284
- and wboi.bizplace_id = $2
285
- and wboi.group_type = 'SINGLE'
286
- and wboi.product_id = $3
287
- and wboi.packing_type = $4
288
- and wboi.packing_size = $5
289
- and wboi.uom = $6
290
- and lockInv.lock_inventory is not true
291
- and (wboi.remain_qty - wboi.transfer_qty - coalesce(storageInv.storage_qty, 0)) >= $7
292
- and (wboi.remain_uom_value - wboi.transfer_uom_value - coalesce(storageInv.storage_uom_value, 0)) >= $8
293
- `, [
294
- domain.id,
295
- customerBizplace.id,
296
- item.product.id,
297
- item.packingType,
298
- item.packingSize,
299
- item.uom,
300
- item.releaseQty,
301
- item.releaseUomValue
302
- ]);
303
- if (itemList.length <= 0) {
304
- throw new error_util_1.ApiError('E01', 'INSUFFICIENT_STOCK');
305
- }
306
- console.log();
307
- }
320
+ const warehouseName = item.warehouseCode ? String(item.warehouseCode).trim() : null;
321
+ const { query, params } = buildInventoryAvailabilityQuery(item, domain, customerBizplace, {
322
+ warehouseName,
323
+ releaseShelfLifeOverride,
324
+ requireWarehouseJoin: false,
325
+ includeLockInventoryCheck: true
326
+ });
327
+ await validateInventoryAvailability(item, query, params, tx);
308
328
  }));
309
329
  }
310
330
  // inv with batchId and assignment enabled: validate availability by batch before creating order
311
331
  if (!invWithoutBatchId && (worksheetPickingAssignment === null || worksheetPickingAssignment === void 0 ? void 0 : worksheetPickingAssignment.value) === 'true') {
312
332
  await Promise.all(massagedData.combinedItems.map(async (item) => {
313
- var _a;
314
333
  const batchId = (item === null || item === void 0 ? void 0 : item.batchId) && item.batchId !== '-' ? String(item.batchId).trim() : null;
315
334
  if (!batchId)
316
335
  return;
317
336
  const warehouseName = (item === null || item === void 0 ? void 0 : item.warehouseCode) ? String(item.warehouseCode).trim() : null;
318
- const rows = await tx.query(`
319
- select
320
- coalesce(sum(
321
- case when (iv.qty - greatest(coalesce(iv.locked_qty,0),0) - greatest(coalesce(pds.unassigned_qty,0),0)) < 0
322
- then 0
323
- else (iv.qty - greatest(coalesce(iv.locked_qty,0),0) - greatest(coalesce(pds.unassigned_qty,0),0))
324
- end
325
- ), 0) as total_available_qty
326
- from inventories iv
327
- left join product_detail_stocks pds on pds.product_detail_id = iv.product_detail_id
328
- inner join locations loc on loc.id = iv.location_id
329
- inner join warehouses w on w.id = loc.warehouse_id
330
- inner join products p on p.id = iv.product_id
331
- where iv.domain_id = $1
332
- and iv.bizplace_id = $2
333
- and iv.product_id = $3
334
- and iv.packing_type = $4
335
- and iv.packing_size = $5
336
- and iv.uom = $6
337
- and iv.status = 'STORED'
338
- and loc.type not in ('QUARANTINE','RESERVE','DAMAGE','STORAGE')
339
- and iv.obsolete = false
340
- and (case when iv.expiration_date is not null and p.min_outbound_shelf_life is not null then CURRENT_DATE < iv.expiration_date - p.min_outbound_shelf_life else true end)
341
- and iv.batch_id = $7
342
- and ( $8::text is null or w.name = $8::text )
343
- `, [
344
- domain.id,
345
- customerBizplace.id,
346
- item.product.id,
347
- item.packingType,
348
- item.packingSize,
349
- item.uom,
337
+ const { query, params } = buildInventoryAvailabilityQuery(item, domain, customerBizplace, {
350
338
  batchId,
351
- warehouseName
352
- ]);
353
- const totalQty = parseFloat(((_a = rows === null || rows === void 0 ? void 0 : rows[0]) === null || _a === void 0 ? void 0 : _a.total_available_qty) || '0');
354
- let reqQty = item.releaseQty;
355
- // normalize comparison for decimal vs non-decimal products
356
- if (!item.product.isInventoryDecimal) {
357
- reqQty = Math.round(reqQty);
358
- }
359
- else {
360
- reqQty = Math.round(reqQty * 1000) / 1000;
361
- }
362
- if (totalQty + 1e-6 < reqQty) {
363
- throw new error_util_1.ApiError('E01', 'INSUFFICIENT_STOCK');
364
- }
339
+ warehouseName,
340
+ releaseShelfLifeOverride,
341
+ requireWarehouseJoin: true,
342
+ includeLockInventoryCheck: false
343
+ });
344
+ await validateInventoryAvailability(item, query, params, tx);
365
345
  }));
366
346
  }
367
347
  // assignment
368
348
  // console.time('assign')
369
- let assignedOrderProducts = await assignToInventory(massagedData.orderProducts, customerBizplace, context, tx, worksheetPickingAssignment);
349
+ let assignedOrderProducts = await assignToInventory(massagedData.orderProducts, customerBizplace, context, tx, worksheetPickingAssignment, releaseShelfLifeOverride);
370
350
  // console.timeEnd('assign')
371
351
  // create order
372
352
  // console.time('save')
@@ -589,6 +569,7 @@ async function createReleaseGood(releaseGood, orderProducts, bizplace, context,
589
569
  : sales_base_1.OrderNoGenerator.releaseGood(),
590
570
  domain: domain,
591
571
  bizplace: bizplace,
572
+ deliverTo: (releaseGood === null || releaseGood === void 0 ? void 0 : releaseGood.deliverTo) || null,
592
573
  collectionOrderNo: releaseGood.collectionOrderNo,
593
574
  courierOption: courierOption,
594
575
  codOption: releaseGood === null || releaseGood === void 0 ? void 0 : releaseGood.codOption,
@@ -951,7 +932,7 @@ async function massageOrderItems(releaseGood, inputOrderProducts, context) {
951
932
  mapJsonData(releaseGood, orderProducts);
952
933
  return { orderProducts, combinedItems };
953
934
  }
954
- async function assignToInventory(orderProducts, customerBizplace, context, tx, worksheetPickingAssignment) {
935
+ async function assignToInventory(orderProducts, customerBizplace, context, tx, worksheetPickingAssignment, releaseShelfLifeOverride) {
955
936
  var _a, _b;
956
937
  const { domain, user } = context.state;
957
938
  const pickingProductSetting = await tx.getRepository(setting_base_1.Setting).findOne({
@@ -1069,6 +1050,9 @@ async function assignToInventory(orderProducts, customerBizplace, context, tx, w
1069
1050
  if ((_b = orderInventory[oiIdx]) === null || _b === void 0 ? void 0 : _b.warehouseCode) {
1070
1051
  params.push(String(orderInventory[oiIdx].warehouseCode).trim());
1071
1052
  }
1053
+ // add release shelf life override parameter
1054
+ const releaseShelfLifeParamIndex = params.length + 1;
1055
+ params.push(releaseShelfLifeOverride);
1072
1056
  let query = `
1073
1057
  update inventories tgt set locked_qty = coalesce(locked_qty,0) + src.reserve_qty,
1074
1058
  locked_uom_value = coalesce(locked_uom_value,0) + src.reserve_uom_value,
@@ -1110,7 +1094,12 @@ async function assignToInventory(orderProducts, customerBizplace, context, tx, w
1110
1094
  "iv"."domain_id" = $2 AND "iv"."bizplace_id" = $3 AND "iv"."packing_type" = $4 AND "iv"."packing_size" = $5 AND "iv"."product_id" = $6 AND "iv"."status" = $7 AND "loc"."type" NOT IN ($8, $9, $10)
1111
1095
  AND ${batchId ? `"iv"."batch_id" = $11` : `1=1`}
1112
1096
  ${warehouseNameFilter}
1113
- AND "iv"."obsolete" = false AND case when "iv"."expiration_date" is not null and "p"."min_outbound_shelf_life" is not null then CURRENT_DATE < "iv"."expiration_date" - "p"."min_outbound_shelf_life" else true end
1097
+ AND "iv"."obsolete" = false AND case
1098
+ when "iv"."expiration_date" is not null
1099
+ and coalesce($${releaseShelfLifeParamIndex}::integer, "p"."min_outbound_shelf_life") is not null
1100
+ then CURRENT_DATE < "iv"."expiration_date" - coalesce($${releaseShelfLifeParamIndex}::integer, "p"."min_outbound_shelf_life")
1101
+ else true
1102
+ end
1114
1103
  ${queryStrings.query.length > 0 ? `AND ${queryStrings.join(' AND ')}` : ''}
1115
1104
  ORDER BY wiar.rank ${sortQuery ? ', ' + sortQuery : ''}
1116
1105
  )