@things-factory/operato-hub 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.
@@ -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,148 @@ 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 (
264
+ iv.expiration_date is null
265
+ or
266
+ case
267
+ when ${shelfLifeParamIndex}::integer is not null and ${shelfLifeParamIndex}::integer > 0 then
268
+ CURRENT_DATE < iv.expiration_date - ${shelfLifeParamIndex}::integer
269
+ when p.min_outbound_shelf_life is not null and p.min_outbound_shelf_life > 0 then
270
+ CURRENT_DATE < iv.expiration_date - p.min_outbound_shelf_life
271
+ else
272
+ true
273
+ end
274
+ )
275
+ ${batchFilter}
276
+ ${warehouseFilter}
277
+ ${lockInventoryCheck}
278
+ `;
279
+ // Build parameters array
280
+ const params = [
281
+ domain.id,
282
+ customerBizplace.id,
283
+ item.product.id,
284
+ item.packingType,
285
+ item.packingSize,
286
+ item.uom
287
+ ];
288
+ if (hasBatch) {
289
+ params.push(batchId);
290
+ }
291
+ if (requireWarehouseJoin || hasWarehouse) {
292
+ params.push(warehouseName);
293
+ }
294
+ params.push(releaseShelfLifeOverride);
295
+ return { query, params };
296
+ };
297
+ // Helper function to validate inventory availability
298
+ const validateInventoryAvailability = async (item, query, params, tx) => {
299
+ var _a, _b;
300
+ const rows = await tx.query(query, params);
301
+ const totalQty = parseFloat(((_a = rows === null || rows === void 0 ? void 0 : rows[0]) === null || _a === void 0 ? void 0 : _a.total_available_qty) || '0');
302
+ 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');
303
+ let reqQty = item.releaseQty;
304
+ let reqUomVal = item.releaseUomValue;
305
+ if (!item.product.isInventoryDecimal) {
306
+ reqQty = Math.round(reqQty);
307
+ }
308
+ else {
309
+ reqQty = Math.round(reqQty * 1000) / 1000;
310
+ }
311
+ if (totalQty + 1e-6 < reqQty || totalUomVal + 1e-6 < reqUomVal) {
312
+ throw new error_util_1.ApiError('E01', 'INSUFFICIENT_STOCK');
313
+ }
314
+ };
172
315
  //inv without batchId will check stock in wboi
173
316
  if (invWithoutBatchId) {
174
317
  // validation
175
318
  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
- }
319
+ const warehouseName = item.warehouseCode ? String(item.warehouseCode).trim() : null;
320
+ const { query, params } = buildInventoryAvailabilityQuery(item, domain, customerBizplace, {
321
+ warehouseName,
322
+ releaseShelfLifeOverride,
323
+ requireWarehouseJoin: false,
324
+ includeLockInventoryCheck: true
325
+ });
326
+ await validateInventoryAvailability(item, query, params, tx);
308
327
  }));
309
328
  }
310
329
  // inv with batchId and assignment enabled: validate availability by batch before creating order
311
330
  if (!invWithoutBatchId && (worksheetPickingAssignment === null || worksheetPickingAssignment === void 0 ? void 0 : worksheetPickingAssignment.value) === 'true') {
312
331
  await Promise.all(massagedData.combinedItems.map(async (item) => {
313
- var _a;
314
332
  const batchId = (item === null || item === void 0 ? void 0 : item.batchId) && item.batchId !== '-' ? String(item.batchId).trim() : null;
315
333
  if (!batchId)
316
334
  return;
317
335
  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,
336
+ const { query, params } = buildInventoryAvailabilityQuery(item, domain, customerBizplace, {
350
337
  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
- }
338
+ warehouseName,
339
+ releaseShelfLifeOverride,
340
+ requireWarehouseJoin: true,
341
+ includeLockInventoryCheck: false
342
+ });
343
+ await validateInventoryAvailability(item, query, params, tx);
365
344
  }));
366
345
  }
367
346
  // assignment
368
347
  // console.time('assign')
369
- let assignedOrderProducts = await assignToInventory(massagedData.orderProducts, customerBizplace, context, tx, worksheetPickingAssignment);
348
+ let assignedOrderProducts = await assignToInventory(massagedData.orderProducts, customerBizplace, context, tx, worksheetPickingAssignment, releaseShelfLifeOverride);
370
349
  // console.timeEnd('assign')
371
350
  // create order
372
351
  // console.time('save')
@@ -589,6 +568,7 @@ async function createReleaseGood(releaseGood, orderProducts, bizplace, context,
589
568
  : sales_base_1.OrderNoGenerator.releaseGood(),
590
569
  domain: domain,
591
570
  bizplace: bizplace,
571
+ deliverTo: (releaseGood === null || releaseGood === void 0 ? void 0 : releaseGood.deliverTo) || null,
592
572
  collectionOrderNo: releaseGood.collectionOrderNo,
593
573
  courierOption: courierOption,
594
574
  codOption: releaseGood === null || releaseGood === void 0 ? void 0 : releaseGood.codOption,
@@ -951,7 +931,7 @@ async function massageOrderItems(releaseGood, inputOrderProducts, context) {
951
931
  mapJsonData(releaseGood, orderProducts);
952
932
  return { orderProducts, combinedItems };
953
933
  }
954
- async function assignToInventory(orderProducts, customerBizplace, context, tx, worksheetPickingAssignment) {
934
+ async function assignToInventory(orderProducts, customerBizplace, context, tx, worksheetPickingAssignment, releaseShelfLifeOverride) {
955
935
  var _a, _b;
956
936
  const { domain, user } = context.state;
957
937
  const pickingProductSetting = await tx.getRepository(setting_base_1.Setting).findOne({
@@ -1069,6 +1049,9 @@ async function assignToInventory(orderProducts, customerBizplace, context, tx, w
1069
1049
  if ((_b = orderInventory[oiIdx]) === null || _b === void 0 ? void 0 : _b.warehouseCode) {
1070
1050
  params.push(String(orderInventory[oiIdx].warehouseCode).trim());
1071
1051
  }
1052
+ // add release shelf life override parameter
1053
+ const releaseShelfLifeParamIndex = params.length + 1;
1054
+ params.push(releaseShelfLifeOverride);
1072
1055
  let query = `
1073
1056
  update inventories tgt set locked_qty = coalesce(locked_qty,0) + src.reserve_qty,
1074
1057
  locked_uom_value = coalesce(locked_uom_value,0) + src.reserve_uom_value,
@@ -1110,7 +1093,18 @@ async function assignToInventory(orderProducts, customerBizplace, context, tx, w
1110
1093
  "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
1094
  AND ${batchId ? `"iv"."batch_id" = $11` : `1=1`}
1112
1095
  ${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
1096
+ AND "iv"."obsolete" = false AND (
1097
+ "iv"."expiration_date" IS NULL
1098
+ OR
1099
+ CASE
1100
+ WHEN $${releaseShelfLifeParamIndex}::integer IS NOT NULL AND $${releaseShelfLifeParamIndex}::integer > 0 THEN
1101
+ CURRENT_DATE < ("iv"."expiration_date" - $${releaseShelfLifeParamIndex}::integer)
1102
+ WHEN "p"."min_outbound_shelf_life" IS NOT NULL AND "p"."min_outbound_shelf_life" > 0 THEN
1103
+ CURRENT_DATE < ("iv"."expiration_date" - "p"."min_outbound_shelf_life")
1104
+ ELSE
1105
+ TRUE
1106
+ END
1107
+ )
1114
1108
  ${queryStrings.query.length > 0 ? `AND ${queryStrings.join(' AND ')}` : ''}
1115
1109
  ORDER BY wiar.rank ${sortQuery ? ', ' + sortQuery : ''}
1116
1110
  )