@things-factory/operato-hub 4.3.700 → 4.3.701

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,22 +44,6 @@ 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
- }
63
47
  const worksheetPickingAssignment = await tx.getRepository(setting_base_1.Setting).findOne({
64
48
  where: { domain, category: 'id-rule', name: 'enable-worksheet-picking-activation-assignment' }
65
49
  });
@@ -78,12 +62,7 @@ api_1.restfulApiRouter.post(`/${apiVersion}/warehouse/add-release-order`, middle
78
62
  salesType: ((_e = bodyReq.xilnexSetting) === null || _e === void 0 ? void 0 : _e.salesType) || ''
79
63
  });
80
64
  }
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;
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;
87
66
  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;
88
67
  releaseGood = {
89
68
  domain,
@@ -103,36 +82,23 @@ api_1.restfulApiRouter.post(`/${apiVersion}/warehouse/add-release-order`, middle
103
82
  type: bodyReq.type,
104
83
  marketplaceOrderStatus: bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.marketplaceOrderStatus,
105
84
  remark: (bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.remark) || 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,
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,
116
88
  deliveryAddress3: ((_l = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _l === void 0 ? void 0 : _l.deliveryAddress3) || null,
117
89
  deliveryAddress4: ((_m = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _m === void 0 ? void 0 : _m.deliveryAddress4) || null,
118
90
  deliveryAddress5: ((_o = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _o === void 0 ? void 0 : _o.deliveryAddress5) || 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,
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,
126
94
  ward: (_s = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _s === void 0 ? void 0 : _s.ward,
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,
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,
133
99
  phone1: newPhone1,
134
100
  phone2: newPhone2,
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,
101
+ email: ((_x = bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.deliverTo) === null || _x === void 0 ? void 0 : _x.email) || null,
136
102
  transporter: bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.transporter,
137
103
  trackingNo: bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.trackingNo,
138
104
  airwayBill: bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.airwayBill,
@@ -152,7 +118,6 @@ api_1.restfulApiRouter.post(`/${apiVersion}/warehouse/add-release-order`, middle
152
118
  shippingFee: bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.shippingFee,
153
119
  lmdOption: bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.lmdOption,
154
120
  priorityDelivery: bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.priorityDelivery,
155
- deliverTo: deliverToContactPoint || null,
156
121
  shippingOrder: (bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.shippingOrder)
157
122
  ? {
158
123
  shipName: bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.shippingOrder.shipName,
@@ -204,149 +169,204 @@ api_1.restfulApiRouter.post(`/${apiVersion}/warehouse/add-release-order`, middle
204
169
  // massage data
205
170
  massagedData = await massageOrderItems(releaseGood, bodyReq.orderInventories, context); //double check this function
206
171
  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
- };
316
172
  //inv without batchId will check stock in wboi
317
173
  if (invWithoutBatchId) {
318
174
  // validation
319
175
  await Promise.all(massagedData.combinedItems.map(async (item) => {
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);
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
+ }
328
308
  }));
329
309
  }
330
310
  // inv with batchId and assignment enabled: validate availability by batch before creating order
331
311
  if (!invWithoutBatchId && (worksheetPickingAssignment === null || worksheetPickingAssignment === void 0 ? void 0 : worksheetPickingAssignment.value) === 'true') {
332
312
  await Promise.all(massagedData.combinedItems.map(async (item) => {
313
+ var _a;
333
314
  const batchId = (item === null || item === void 0 ? void 0 : item.batchId) && item.batchId !== '-' ? String(item.batchId).trim() : null;
334
315
  if (!batchId)
335
316
  return;
336
317
  const warehouseName = (item === null || item === void 0 ? void 0 : item.warehouseCode) ? String(item.warehouseCode).trim() : null;
337
- const { query, params } = buildInventoryAvailabilityQuery(item, domain, customerBizplace, {
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,
338
350
  batchId,
339
- warehouseName,
340
- releaseShelfLifeOverride,
341
- requireWarehouseJoin: true,
342
- includeLockInventoryCheck: false
343
- });
344
- await validateInventoryAvailability(item, query, params, tx);
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
+ }
345
365
  }));
346
366
  }
347
367
  // assignment
348
368
  // console.time('assign')
349
- let assignedOrderProducts = await assignToInventory(massagedData.orderProducts, customerBizplace, context, tx, worksheetPickingAssignment, releaseShelfLifeOverride);
369
+ let assignedOrderProducts = await assignToInventory(massagedData.orderProducts, customerBizplace, context, tx, worksheetPickingAssignment);
350
370
  // console.timeEnd('assign')
351
371
  // create order
352
372
  // console.time('save')
@@ -569,7 +589,6 @@ async function createReleaseGood(releaseGood, orderProducts, bizplace, context,
569
589
  : sales_base_1.OrderNoGenerator.releaseGood(),
570
590
  domain: domain,
571
591
  bizplace: bizplace,
572
- deliverTo: (releaseGood === null || releaseGood === void 0 ? void 0 : releaseGood.deliverTo) || null,
573
592
  collectionOrderNo: releaseGood.collectionOrderNo,
574
593
  courierOption: courierOption,
575
594
  codOption: releaseGood === null || releaseGood === void 0 ? void 0 : releaseGood.codOption,
@@ -932,7 +951,7 @@ async function massageOrderItems(releaseGood, inputOrderProducts, context) {
932
951
  mapJsonData(releaseGood, orderProducts);
933
952
  return { orderProducts, combinedItems };
934
953
  }
935
- async function assignToInventory(orderProducts, customerBizplace, context, tx, worksheetPickingAssignment, releaseShelfLifeOverride) {
954
+ async function assignToInventory(orderProducts, customerBizplace, context, tx, worksheetPickingAssignment) {
936
955
  var _a, _b;
937
956
  const { domain, user } = context.state;
938
957
  const pickingProductSetting = await tx.getRepository(setting_base_1.Setting).findOne({
@@ -1050,9 +1069,6 @@ async function assignToInventory(orderProducts, customerBizplace, context, tx, w
1050
1069
  if ((_b = orderInventory[oiIdx]) === null || _b === void 0 ? void 0 : _b.warehouseCode) {
1051
1070
  params.push(String(orderInventory[oiIdx].warehouseCode).trim());
1052
1071
  }
1053
- // add release shelf life override parameter
1054
- const releaseShelfLifeParamIndex = params.length + 1;
1055
- params.push(releaseShelfLifeOverride);
1056
1072
  let query = `
1057
1073
  update inventories tgt set locked_qty = coalesce(locked_qty,0) + src.reserve_qty,
1058
1074
  locked_uom_value = coalesce(locked_uom_value,0) + src.reserve_uom_value,
@@ -1094,12 +1110,7 @@ async function assignToInventory(orderProducts, customerBizplace, context, tx, w
1094
1110
  "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)
1095
1111
  AND ${batchId ? `"iv"."batch_id" = $11` : `1=1`}
1096
1112
  ${warehouseNameFilter}
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
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
1103
1114
  ${queryStrings.query.length > 0 ? `AND ${queryStrings.join(' AND ')}` : ''}
1104
1115
  ORDER BY wiar.rank ${sortQuery ? ', ' + sortQuery : ''}
1105
1116
  )