@things-factory/operato-hub 4.3.744 → 4.3.745

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.
Files changed (25) hide show
  1. package/dist-server/routers/api/restful-apis/v1/company/add-contact-points.js +71 -2
  2. package/dist-server/routers/api/restful-apis/v1/company/add-contact-points.js.map +1 -1
  3. package/dist-server/routers/api/restful-apis/v1/company/index.js +1 -0
  4. package/dist-server/routers/api/restful-apis/v1/company/index.js.map +1 -1
  5. package/dist-server/routers/api/restful-apis/v1/company/update-contact-points.js +243 -0
  6. package/dist-server/routers/api/restful-apis/v1/company/update-contact-points.js.map +1 -0
  7. package/dist-server/routers/api/restful-apis/v1/utils/params.js +109 -28
  8. package/dist-server/routers/api/restful-apis/v1/utils/params.js.map +1 -1
  9. package/dist-server/routers/api/restful-apis/v1/warehouse/index.js +1 -0
  10. package/dist-server/routers/api/restful-apis/v1/warehouse/index.js.map +1 -1
  11. package/dist-server/routers/api/restful-apis/v1/warehouse/update-arrival-notice.js +365 -0
  12. package/dist-server/routers/api/restful-apis/v1/warehouse/update-arrival-notice.js.map +1 -0
  13. package/dist-server/routers/api/restful-apis/v1/warehouse/update-release-order-details.js +946 -19
  14. package/dist-server/routers/api/restful-apis/v1/warehouse/update-release-order-details.js.map +1 -1
  15. package/openapi/v1/contact-point.yaml +266 -0
  16. package/openapi/v1/inbound.yaml +349 -0
  17. package/openapi/v1/outbound.yaml +256 -150
  18. package/package.json +18 -18
  19. package/server/routers/api/restful-apis/v1/company/add-contact-points.ts +91 -2
  20. package/server/routers/api/restful-apis/v1/company/index.ts +1 -0
  21. package/server/routers/api/restful-apis/v1/company/update-contact-points.ts +267 -0
  22. package/server/routers/api/restful-apis/v1/utils/params.ts +109 -28
  23. package/server/routers/api/restful-apis/v1/warehouse/index.ts +1 -0
  24. package/server/routers/api/restful-apis/v1/warehouse/update-arrival-notice.ts +429 -0
  25. package/server/routers/api/restful-apis/v1/warehouse/update-release-order-details.ts +1122 -29
@@ -1,12 +1,47 @@
1
1
  "use strict";
2
+ var __rest = (this && this.__rest) || function (s, e) {
3
+ var t = {};
4
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
+ t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
+ t[p[i]] = s[p[i]];
10
+ }
11
+ return t;
12
+ };
2
13
  Object.defineProperty(exports, "__esModule", { value: true });
3
14
  const typeorm_1 = require("typeorm");
15
+ const uuid_1 = require("uuid");
4
16
  const api_1 = require("@things-factory/api");
5
17
  const biz_base_1 = require("@things-factory/biz-base");
18
+ const product_base_1 = require("@things-factory/product-base");
6
19
  const sales_base_1 = require("@things-factory/sales-base");
20
+ const setting_base_1 = require("@things-factory/setting-base");
21
+ const warehouse_base_1 = require("@things-factory/warehouse-base");
22
+ const worksheet_base_1 = require("@things-factory/worksheet-base");
7
23
  const middlewares_1 = require("../middlewares");
8
24
  const error_util_1 = require("../utils/error-util");
9
25
  const debug = require('debug')('things-factory:operato-hub:restful-api:v1:update-release-order-details');
26
+ // Allowed statuses for updates (order information only)
27
+ const ALLOWED_STATUSES_FOR_INFO_UPDATE = [
28
+ sales_base_1.ORDER_STATUS.PENDING,
29
+ sales_base_1.ORDER_STATUS.PENDING_RECEIVE,
30
+ sales_base_1.ORDER_STATUS.PENDING_WORKSHEET,
31
+ sales_base_1.ORDER_STATUS.READY_TO_PICK,
32
+ sales_base_1.ORDER_STATUS.PICKING,
33
+ sales_base_1.ORDER_STATUS.READY_TO_LOAD,
34
+ sales_base_1.ORDER_STATUS.LOADING,
35
+ sales_base_1.ORDER_STATUS.READY_TO_PACK,
36
+ sales_base_1.ORDER_STATUS.PACKING
37
+ ];
38
+ // Allowed statuses for order product updates
39
+ const ALLOWED_STATUSES_FOR_PRODUCT_UPDATE = [
40
+ sales_base_1.ORDER_STATUS.PENDING,
41
+ sales_base_1.ORDER_STATUS.PENDING_RECEIVE,
42
+ sales_base_1.ORDER_STATUS.PENDING_WORKSHEET,
43
+ sales_base_1.ORDER_STATUS.READY_TO_PICK
44
+ ];
10
45
  api_1.restfulApiRouter.post('/v1/warehouse/update-release-order-details', middlewares_1.businessMiddleware, middlewares_1.validationMiddleware, middlewares_1.loggingMiddleware, async (context, next) => {
11
46
  try {
12
47
  const { domain } = context.state;
@@ -18,11 +53,12 @@ api_1.restfulApiRouter.post('/v1/warehouse/update-release-order-details', middle
18
53
  throw new error_util_1.ApiError('E04', 'warehouse id not found');
19
54
  let patch = {};
20
55
  patch.releaseGood = bodyReq.releaseGood;
56
+ patch.orderProducts = bodyReq.orderProducts || [];
21
57
  if (bodyReq === null || bodyReq === void 0 ? void 0 : bodyReq.shippingOrder)
22
58
  patch.shippingOrder = bodyReq.shippingOrder;
23
59
  else
24
60
  patch.shippingOrder = null;
25
- context.body = await updateReleaseGoodDetails(tx, domain, patch, user);
61
+ context.body = await updateReleaseGoodDetails(tx, domain, patch, user, context);
26
62
  });
27
63
  }
28
64
  catch (e) {
@@ -32,36 +68,42 @@ api_1.restfulApiRouter.post('/v1/warehouse/update-release-order-details', middle
32
68
  (0, error_util_1.throwInternalServerError)(context, e);
33
69
  }
34
70
  });
35
- async function updateReleaseGoodDetails(tx, domain, patch, user) {
36
- let { releaseGood, shippingOrder } = patch;
37
- let status = [sales_base_1.ORDER_STATUS.DONE, sales_base_1.ORDER_STATUS.CANCELLED, sales_base_1.ORDER_STATUS.REJECTED];
71
+ async function updateReleaseGoodDetails(tx, domain, patch, user, context) {
72
+ let { releaseGood, shippingOrder, orderProducts: inputOrderProducts } = patch;
38
73
  // Build where clause: prioritize refOrderId over roId
39
74
  let whereClause = { domain };
40
75
  const refOrderId = releaseGood === null || releaseGood === void 0 ? void 0 : releaseGood.refOrderId;
41
76
  const hasRefOrderId = refOrderId != null && refOrderId !== '';
77
+ const roId = releaseGood === null || releaseGood === void 0 ? void 0 : releaseGood.roId;
42
78
  if (hasRefOrderId) {
43
79
  whereClause.refOrderId = refOrderId;
44
80
  }
45
- else if (releaseGood === null || releaseGood === void 0 ? void 0 : releaseGood.roId) {
46
- whereClause.id = releaseGood.roId;
81
+ else if (roId) {
82
+ whereClause.id = roId;
47
83
  }
48
84
  else {
49
85
  throw new error_util_1.ApiError('E04', 'release order: roId or refOrderId is required');
50
86
  }
51
87
  let foundReleaseGood = await tx.getRepository(sales_base_1.ReleaseGood).findOne({
52
88
  where: whereClause,
53
- relations: ['shippingOrder']
89
+ relations: ['bizplace', 'shippingOrder', 'orderProducts', 'orderProducts.product', 'orderProducts.productDetail']
54
90
  });
55
91
  if (!foundReleaseGood) {
56
- const searchId = hasRefOrderId
57
- ? `refOrderId: ${refOrderId}`
58
- : `roId: ${releaseGood === null || releaseGood === void 0 ? void 0 : releaseGood.roId}`;
92
+ const searchId = hasRefOrderId ? `refOrderId: ${refOrderId}` : `roId: ${roId}`;
59
93
  throw new error_util_1.ApiError('E04', `release order: ${searchId}`);
60
94
  }
61
- if (status.indexOf(foundReleaseGood.status) !== -1) {
62
- throw new error_util_1.ApiError('E04', `release order status: ${foundReleaseGood.status}`);
95
+ // Validate status for order information updates
96
+ const releaseGoodStatus = foundReleaseGood.status;
97
+ if (!ALLOWED_STATUSES_FOR_INFO_UPDATE.includes(releaseGoodStatus)) {
98
+ throw new error_util_1.ApiError('E04', `Release order status "${releaseGoodStatus}" does not allow updates. Allowed statuses: ${ALLOWED_STATUSES_FOR_INFO_UPDATE.join(', ')}`);
99
+ }
100
+ // Validate status for order product updates
101
+ if (inputOrderProducts && inputOrderProducts.length > 0) {
102
+ if (!ALLOWED_STATUSES_FOR_PRODUCT_UPDATE.includes(releaseGoodStatus)) {
103
+ throw new error_util_1.ApiError('E04', `Order product updates are not allowed when release order status is "${releaseGoodStatus}". Order products can only be updated when status is: ${ALLOWED_STATUSES_FOR_PRODUCT_UPDATE.join(', ')}`);
104
+ }
63
105
  }
64
- // case to update existing shippingOrder
106
+ // Update shipping order if provided
65
107
  if (shippingOrder !== null) {
66
108
  shippingOrder.remark = shippingOrder.exportRemark;
67
109
  shippingOrder.containerClosureDateTime = shippingOrder.containerClosureDate
@@ -69,15 +111,900 @@ async function updateReleaseGoodDetails(tx, domain, patch, user) {
69
111
  : null;
70
112
  delete shippingOrder.exportRemark;
71
113
  }
72
- if (foundReleaseGood.shippingOrder && shippingOrder) {
73
- await tx.getRepository(sales_base_1.ShippingOrder).update(foundReleaseGood.shippingOrder.id, shippingOrder);
114
+ const releaseGoodEntityForShipping = foundReleaseGood;
115
+ const foundShippingOrder = releaseGoodEntityForShipping.shippingOrder;
116
+ if (foundShippingOrder && shippingOrder) {
117
+ await tx.getRepository(sales_base_1.ShippingOrder).update(foundShippingOrder.id, shippingOrder);
74
118
  }
75
- // case for new shippingOrder
76
- else if (!foundReleaseGood.shippingOrder && shippingOrder) {
119
+ else if (!foundShippingOrder && shippingOrder) {
77
120
  let newShippingOrder = await tx.getRepository(sales_base_1.ShippingOrder).save(Object.assign(Object.assign({}, shippingOrder), { name: sales_base_1.OrderNoGenerator.shippingOrder(), domain, bizplace: await (0, biz_base_1.getMyBizplace)(domain, user), status: sales_base_1.ORDER_STATUS.PENDING, creator: user, updater: user }));
78
121
  releaseGood.shippingOrder = newShippingOrder;
79
122
  }
80
- foundReleaseGood = await tx.getRepository(sales_base_1.ReleaseGood).save(Object.assign(Object.assign(Object.assign({}, foundReleaseGood), releaseGood), { remark: (releaseGood === null || releaseGood === void 0 ? void 0 : releaseGood.remark) ? releaseGood.remark : null }));
81
- return foundReleaseGood;
123
+ // Update order products if provided
124
+ if (inputOrderProducts && inputOrderProducts.length > 0) {
125
+ await updateOrderProducts(tx, foundReleaseGood, inputOrderProducts, domain, user, context);
126
+ }
127
+ // Update release good fields
128
+ const updateData = {
129
+ remark: (releaseGood === null || releaseGood === void 0 ? void 0 : releaseGood.remark) ? releaseGood.remark : null,
130
+ updater: user
131
+ };
132
+ const releaseGoodPatch = releaseGood;
133
+ if ((releaseGoodPatch === null || releaseGoodPatch === void 0 ? void 0 : releaseGoodPatch.refNo) !== undefined)
134
+ updateData.refNo = releaseGoodPatch.refNo;
135
+ if ((releaseGoodPatch === null || releaseGoodPatch === void 0 ? void 0 : releaseGoodPatch.refNo2) !== undefined)
136
+ updateData.refNo2 = releaseGoodPatch.refNo2;
137
+ if ((releaseGoodPatch === null || releaseGoodPatch === void 0 ? void 0 : releaseGoodPatch.refNo3) !== undefined)
138
+ updateData.refNo3 = releaseGoodPatch.refNo3;
139
+ if ((releaseGoodPatch === null || releaseGoodPatch === void 0 ? void 0 : releaseGoodPatch.description) !== undefined)
140
+ updateData.description = releaseGoodPatch.description;
141
+ if ((releaseGoodPatch === null || releaseGoodPatch === void 0 ? void 0 : releaseGoodPatch.truckNo) !== undefined)
142
+ updateData.truckNo = releaseGoodPatch.truckNo;
143
+ if ((releaseGoodPatch === null || releaseGoodPatch === void 0 ? void 0 : releaseGoodPatch.collectionOrderNo) !== undefined)
144
+ updateData.collectionOrderNo = releaseGoodPatch.collectionOrderNo;
145
+ if ((releaseGoodPatch === null || releaseGoodPatch === void 0 ? void 0 : releaseGoodPatch.releaseDate) !== undefined)
146
+ updateData.releaseDate = releaseGoodPatch.releaseDate;
147
+ if ((releaseGoodPatch === null || releaseGoodPatch === void 0 ? void 0 : releaseGoodPatch.ownTransport) !== undefined)
148
+ updateData.ownTransport = releaseGoodPatch.ownTransport;
149
+ if ((releaseGoodPatch === null || releaseGoodPatch === void 0 ? void 0 : releaseGoodPatch.exportOption) !== undefined)
150
+ updateData.exportOption = releaseGoodPatch.exportOption;
151
+ if ((releaseGoodPatch === null || releaseGoodPatch === void 0 ? void 0 : releaseGoodPatch.packingOption) !== undefined)
152
+ updateData.packingOption = releaseGoodPatch.packingOption;
153
+ if ((releaseGoodPatch === null || releaseGoodPatch === void 0 ? void 0 : releaseGoodPatch.courierOption) !== undefined)
154
+ updateData.courierOption = releaseGoodPatch.courierOption;
155
+ if ((releaseGoodPatch === null || releaseGoodPatch === void 0 ? void 0 : releaseGoodPatch.shippingOrder) !== undefined)
156
+ updateData.shippingOrder = releaseGoodPatch.shippingOrder;
157
+ const releaseGoodEntityForUpdate = foundReleaseGood;
158
+ // Exclude orderProducts relation to prevent TypeORM from syncing and clearing foreign keys
159
+ const { orderProducts, orderInventories, deliveryOrders } = releaseGoodEntityForUpdate, releaseGoodWithoutRelations = __rest(releaseGoodEntityForUpdate, ["orderProducts", "orderInventories", "deliveryOrders"]);
160
+ await tx.getRepository(sales_base_1.ReleaseGood).save(Object.assign(Object.assign({}, releaseGoodWithoutRelations), updateData));
161
+ // Fetch updated release good with relations for response
162
+ // Query order products separately to ensure we get all of them including newly added ones
163
+ const releaseGoodId = foundReleaseGood.id;
164
+ // Fetch release good basic info
165
+ const updatedReleaseGood = await tx.getRepository(sales_base_1.ReleaseGood).findOne({
166
+ where: { id: releaseGoodId },
167
+ relations: ['shippingOrder']
168
+ });
169
+ if (!updatedReleaseGood) {
170
+ throw new error_util_1.ApiError('E04', 'Failed to retrieve updated release order');
171
+ }
172
+ // Fetch all order products separately to ensure we get newly added ones
173
+ const allOrderProducts = await tx.getRepository(sales_base_1.OrderProduct).find({
174
+ where: { releaseGood: { id: releaseGoodId } },
175
+ relations: ['product', 'productDetail']
176
+ });
177
+ // Attach order products to release good
178
+ updatedReleaseGood.orderProducts = allOrderProducts;
179
+ const releaseGoodForResponse = updatedReleaseGood;
180
+ // Format response - only include necessary fields
181
+ const resultOrderProducts = (releaseGoodForResponse.orderProducts || []).map((op) => {
182
+ var _a, _b, _c;
183
+ return ({
184
+ id: op.id,
185
+ product: {
186
+ sku: ((_a = op.product) === null || _a === void 0 ? void 0 : _a.sku) || null,
187
+ name: ((_b = op.product) === null || _b === void 0 ? void 0 : _b.name) || null
188
+ },
189
+ productDetail: {
190
+ refCode: ((_c = op.productDetail) === null || _c === void 0 ? void 0 : _c.refCode) || null
191
+ },
192
+ batchId: op.batchId,
193
+ packingType: op.packingType,
194
+ packingSize: op.packingSize,
195
+ releaseQty: op.releaseQty,
196
+ releaseUomValue: op.releaseUomValue,
197
+ uom: op.uom,
198
+ refItemId: op.refItemId || null,
199
+ unitPrice: op.unitPrice || null,
200
+ remark: op.remark || null,
201
+ status: op.status
202
+ });
203
+ });
204
+ const shippingOrderData = releaseGoodForResponse.shippingOrder
205
+ ? {
206
+ id: releaseGoodForResponse.shippingOrder.id,
207
+ name: releaseGoodForResponse.shippingOrder.name,
208
+ status: releaseGoodForResponse.shippingOrder.status
209
+ }
210
+ : null;
211
+ const data = {
212
+ id: releaseGoodForResponse.id,
213
+ roNo: releaseGoodForResponse.name,
214
+ refOrderId: releaseGoodForResponse.refOrderId,
215
+ refNo: releaseGoodForResponse.refNo,
216
+ refNo2: releaseGoodForResponse.refNo2,
217
+ refNo3: releaseGoodForResponse.refNo3,
218
+ description: releaseGoodForResponse.description,
219
+ truckNo: releaseGoodForResponse.truckNo,
220
+ collectionOrderNo: releaseGoodForResponse.collectionOrderNo,
221
+ releaseDate: releaseGoodForResponse.releaseDate,
222
+ ownTransport: releaseGoodForResponse.ownTransport,
223
+ exportOption: releaseGoodForResponse.exportOption,
224
+ packingOption: releaseGoodForResponse.packingOption,
225
+ courierOption: releaseGoodForResponse.courierOption,
226
+ status: releaseGoodForResponse.status,
227
+ remark: releaseGoodForResponse.remark,
228
+ orderProducts: resultOrderProducts,
229
+ shippingOrder: shippingOrderData
230
+ };
231
+ return {
232
+ responseCode: '200',
233
+ message: 'success',
234
+ data
235
+ };
236
+ }
237
+ async function updateOrderProducts(tx, releaseGood, inputOrderProducts, domain, user, context) {
238
+ // Get existing order products with their order inventories
239
+ const existingOrderProducts = await tx.getRepository(sales_base_1.OrderProduct).find({
240
+ where: { releaseGood },
241
+ relations: ['product', 'productDetail', 'orderInventories', 'orderInventories.inventory']
242
+ });
243
+ // Determine hasOrderInventories by checking if releaseGood has any orderInventories
244
+ // Check directly on releaseGood by counting orderInventories
245
+ const orderInventoriesCount = await tx.getRepository(sales_base_1.OrderInventory).count({
246
+ where: { releaseGood }
247
+ });
248
+ const hasOrderInventories = orderInventoriesCount > 0;
249
+ // Process removals first
250
+ const orderProductIdsToRemove = [];
251
+ const orderProductsToUpdate = [];
252
+ const orderProductsToAdd = [];
253
+ for (const orderProductReq of inputOrderProducts) {
254
+ if (orderProductReq._action === 'remove') {
255
+ const matchingOP = findMatchingOrderProduct(existingOrderProducts, orderProductReq);
256
+ if (!matchingOP) {
257
+ throw new error_util_1.ApiError('E04', `Order product not found for removal. Provide id, sku, refCode, batchId, packingType, or refItemId to identify the item.`);
258
+ }
259
+ orderProductIdsToRemove.push(matchingOP.id);
260
+ continue;
261
+ }
262
+ // Try to find existing order product for update
263
+ const matchingOP = findMatchingOrderProduct(existingOrderProducts, orderProductReq);
264
+ if (matchingOP) {
265
+ orderProductsToUpdate.push({ existing: matchingOP, request: orderProductReq });
266
+ }
267
+ else {
268
+ orderProductsToAdd.push(orderProductReq);
269
+ }
270
+ }
271
+ // Validate: Prevent removing all products from the order
272
+ // If removing products would leave the order with zero products, throw an error
273
+ const existingProductCount = existingOrderProducts.length;
274
+ const productsToRemoveCount = orderProductIdsToRemove.length;
275
+ const productsToAddCount = orderProductsToAdd.length;
276
+ const remainingProductCount = existingProductCount - productsToRemoveCount + productsToAddCount;
277
+ if (remainingProductCount <= 0) {
278
+ throw new error_util_1.ApiError('E04', 'Cannot remove all products from the release order. Please use the cancellation API to cancel the entire order instead of removing the final item(s).');
279
+ }
280
+ // Remove order products
281
+ if (orderProductIdsToRemove.length > 0) {
282
+ await removeOrderProducts(tx, orderProductIdsToRemove, hasOrderInventories);
283
+ }
284
+ // Update existing order products
285
+ for (const { existing, request } of orderProductsToUpdate) {
286
+ await updateExistingOrderProduct(tx, existing, request, releaseGood, domain, user, context, hasOrderInventories);
287
+ }
288
+ // Add new order products
289
+ for (const orderProductReq of orderProductsToAdd) {
290
+ await addNewOrderProduct(tx, orderProductReq, releaseGood, domain, user, context, hasOrderInventories);
291
+ }
292
+ }
293
+ function findMatchingOrderProduct(existingOrderProducts, orderProductReq) {
294
+ var _a, _b, _c;
295
+ // First try by ID if provided
296
+ if (orderProductReq.id) {
297
+ const found = existingOrderProducts.find(op => op.id === orderProductReq.id);
298
+ if (found)
299
+ return found;
300
+ }
301
+ // Extract matching fields
302
+ const sku = ((_a = orderProductReq.product) === null || _a === void 0 ? void 0 : _a.sku) || orderProductReq.sku;
303
+ const refCode = ((_b = orderProductReq.product) === null || _b === void 0 ? void 0 : _b.refCode) || ((_c = orderProductReq.productDetail) === null || _c === void 0 ? void 0 : _c.refCode) || orderProductReq.refCode;
304
+ const batchId = orderProductReq.batchId;
305
+ const packingType = orderProductReq.packingType;
306
+ const refItemId = orderProductReq.refItemId;
307
+ // Need at least one matching field (besides id)
308
+ if (!sku && !refCode && !batchId && !packingType && !refItemId) {
309
+ return null;
310
+ }
311
+ // Match by provided fields (all provided fields must match)
312
+ return (existingOrderProducts.find(op => {
313
+ var _a, _b;
314
+ const matchesSku = !sku || ((_a = op.product) === null || _a === void 0 ? void 0 : _a.sku) === sku;
315
+ const matchesRefCode = !refCode || ((_b = op.productDetail) === null || _b === void 0 ? void 0 : _b.refCode) === refCode;
316
+ const matchesBatchId = !batchId || op.batchId === batchId;
317
+ const matchesPackingType = !packingType || op.packingType === packingType;
318
+ const matchesRefItemId = !refItemId || op.refItemId === refItemId;
319
+ return matchesSku && matchesRefCode && matchesBatchId && matchesPackingType && matchesRefItemId;
320
+ }) || null);
321
+ }
322
+ async function removeOrderProducts(tx, orderProductIds, hasOrderInventories) {
323
+ var _a;
324
+ for (const orderProductId of orderProductIds) {
325
+ const orderProduct = await tx.getRepository(sales_base_1.OrderProduct).findOne({
326
+ where: { id: orderProductId },
327
+ relations: ['orderInventories', 'orderInventories.inventory']
328
+ });
329
+ if (!orderProduct)
330
+ continue;
331
+ const orderProductEntity = orderProduct;
332
+ const orderInventories = (orderProductEntity.orderInventories || []);
333
+ const releaseQty = orderProductEntity.releaseQty || 0;
334
+ const releaseUomValue = orderProductEntity.releaseUomValue || 0;
335
+ if (hasOrderInventories && orderInventories.length > 0) {
336
+ // Release locked quantities from inventories
337
+ for (const orderInventory of orderInventories) {
338
+ const inventory = orderInventory.inventory;
339
+ if (inventory) {
340
+ const oiReleaseQty = orderInventory.releaseQty || 0;
341
+ const oiReleaseUomValue = orderInventory.releaseUomValue || 0;
342
+ const inventoryEntity = inventory;
343
+ await tx
344
+ .getRepository(warehouse_base_1.Inventory)
345
+ .createQueryBuilder()
346
+ .update(warehouse_base_1.Inventory)
347
+ .set({
348
+ lockedQty: () => `COALESCE(locked_qty, 0) - ${oiReleaseQty}`,
349
+ lockedUomValue: () => `COALESCE(locked_uom_value, 0) - ${oiReleaseUomValue}`,
350
+ updater: orderProductEntity.updater
351
+ })
352
+ .where('id = :id', { id: inventoryEntity.id })
353
+ .execute();
354
+ }
355
+ }
356
+ // Delete order inventories
357
+ // First, delete any worksheet details that reference these order inventories
358
+ const oiIds = orderInventories.map(oi => oi.id).filter((id) => !!id);
359
+ if (oiIds.length > 0) {
360
+ // Find and delete worksheet details that reference the order inventories to be deleted
361
+ // Join with OrderInventory to find worksheet details by order inventory IDs
362
+ const worksheetDetailsToDelete = await tx
363
+ .getRepository(worksheet_base_1.WorksheetDetail)
364
+ .createQueryBuilder('wsd')
365
+ .innerJoin('wsd.targetInventory', 'oi')
366
+ .where('oi.id IN (:...oiIds)', { oiIds })
367
+ .andWhere('wsd.domain = :domainId', {
368
+ domainId: ((_a = orderProductEntity.domain) === null || _a === void 0 ? void 0 : _a.id) || orderProductEntity.domainId
369
+ })
370
+ .getMany();
371
+ if (worksheetDetailsToDelete.length > 0) {
372
+ const worksheetDetailIds = worksheetDetailsToDelete.map(wsd => wsd.id);
373
+ await tx.getRepository(worksheet_base_1.WorksheetDetail).delete(worksheetDetailIds);
374
+ }
375
+ // Now delete the order inventories
376
+ await tx.getRepository(sales_base_1.OrderInventory).delete(oiIds);
377
+ }
378
+ }
379
+ else {
380
+ // Update ProductDetailStock unassigned quantities
381
+ const productDetail = orderProductEntity.productDetail;
382
+ if (productDetail && productDetail.id) {
383
+ await tx
384
+ .getRepository(warehouse_base_1.ProductDetailStock)
385
+ .createQueryBuilder()
386
+ .update(warehouse_base_1.ProductDetailStock)
387
+ .set({
388
+ unassignedQty: () => `"unassigned_qty" - ${releaseQty}`,
389
+ unassignedUomValue: () => `"unassigned_uom_value" - ${releaseUomValue}`
390
+ })
391
+ .where({ productDetail: productDetail.id })
392
+ .execute();
393
+ }
394
+ }
395
+ // Delete order product
396
+ await tx.getRepository(sales_base_1.OrderProduct).delete(orderProductId);
397
+ }
398
+ }
399
+ async function updateExistingOrderProduct(tx, existing, request, releaseGood, domain, user, context, hasOrderInventories) {
400
+ var _a, _b, _c, _d, _e;
401
+ const newQty = request.releaseQty !== undefined ? request.releaseQty : request.qty;
402
+ if (newQty === undefined) {
403
+ // No quantity change, just update other fields if provided
404
+ const updateData = { updater: user };
405
+ if (request.remark !== undefined)
406
+ updateData.remark = request.remark;
407
+ if (request.unitPrice !== undefined)
408
+ updateData.unitPrice = request.unitPrice;
409
+ await tx.getRepository(sales_base_1.OrderProduct).update(existing.id, updateData);
410
+ return;
411
+ }
412
+ if (newQty <= 0) {
413
+ throw new error_util_1.ApiError('E01', 'releaseQty must be greater than 0');
414
+ }
415
+ const qtyDiff = newQty - existing.releaseQty;
416
+ const uomValueDiff = (newQty - existing.releaseQty) * (((_a = existing.productDetail) === null || _a === void 0 ? void 0 : _a.uomValue) || 1);
417
+ // Validate decimal places for non-decimal products
418
+ if (!((_b = existing.product) === null || _b === void 0 ? void 0 : _b.isInventoryDecimal) && newQty % 1 !== 0) {
419
+ throw new error_util_1.ApiError('E01', `releaseQty must be an integer for product ${(_c = existing.product) === null || _c === void 0 ? void 0 : _c.sku}`);
420
+ }
421
+ // Round to 3 decimal places
422
+ const roundedNewQty = ((_d = existing.product) === null || _d === void 0 ? void 0 : _d.isInventoryDecimal) ? Math.round(newQty * 1000) / 1000 : Math.round(newQty);
423
+ const roundedNewUomValue = roundedNewQty * (((_e = existing.productDetail) === null || _e === void 0 ? void 0 : _e.uomValue) || 1);
424
+ if (hasOrderInventories) {
425
+ // Update order inventories
426
+ await updateOrderInventoriesForQtyChange(tx, existing, qtyDiff, uomValueDiff, roundedNewQty, roundedNewUomValue, user, releaseGood, domain);
427
+ }
428
+ else {
429
+ // Update ProductDetailStock
430
+ await tx
431
+ .getRepository(warehouse_base_1.ProductDetailStock)
432
+ .createQueryBuilder()
433
+ .update(warehouse_base_1.ProductDetailStock)
434
+ .set({
435
+ unassignedQty: () => `"unassigned_qty" + ${qtyDiff}`,
436
+ unassignedUomValue: () => `"unassigned_uom_value" + ${uomValueDiff}`
437
+ })
438
+ .where({ productDetail: existing.productDetail.id })
439
+ .execute();
440
+ }
441
+ // Update order product
442
+ const updateData = {
443
+ releaseQty: roundedNewQty,
444
+ releaseUomValue: roundedNewUomValue,
445
+ updater: user
446
+ };
447
+ if (request.remark !== undefined)
448
+ updateData.remark = request.remark;
449
+ if (request.unitPrice !== undefined)
450
+ updateData.unitPrice = request.unitPrice;
451
+ await tx.getRepository(sales_base_1.OrderProduct).update(existing.id, updateData);
452
+ }
453
+ async function updateOrderInventoriesForQtyChange(tx, orderProduct, qtyDiff, uomValueDiff, newQty, newUomValue, user, releaseGood, domain) {
454
+ const existingOrderInventories = (await tx.getRepository(sales_base_1.OrderInventory).find({
455
+ where: { orderProduct },
456
+ relations: ['inventory']
457
+ }));
458
+ if (qtyDiff > 0) {
459
+ // Increase: assign more inventory
460
+ await assignAdditionalInventory(tx, orderProduct, qtyDiff, uomValueDiff, user, releaseGood, domain);
461
+ }
462
+ else if (qtyDiff < 0) {
463
+ // Decrease: select latest order inventories and deduct from them
464
+ const reductionQty = Math.abs(qtyDiff);
465
+ const reductionUomValue = Math.abs(uomValueDiff);
466
+ // Sort order inventories by creation date (latest first)
467
+ const sortedOrderInventories = [...existingOrderInventories].sort((a, b) => {
468
+ const dateA = a.createdAt ? new Date(a.createdAt).getTime() : 0;
469
+ const dateB = b.createdAt ? new Date(b.createdAt).getTime() : 0;
470
+ return dateB - dateA; // Latest first
471
+ });
472
+ let remainingReductionQty = reductionQty;
473
+ let remainingReductionUomValue = reductionUomValue;
474
+ const orderInventoriesToDelete = [];
475
+ for (const oi of sortedOrderInventories) {
476
+ if (remainingReductionQty <= 0 && remainingReductionUomValue <= 0) {
477
+ break;
478
+ }
479
+ const oiReleaseQty = oi.releaseQty || 0;
480
+ const oiReleaseUomValue = oi.releaseUomValue || 0;
481
+ // Calculate how much to deduct from this order inventory
482
+ const deductQty = Math.min(remainingReductionQty, oiReleaseQty);
483
+ const deductUomValue = Math.min(remainingReductionUomValue, oiReleaseUomValue);
484
+ const inventory = oi.inventory;
485
+ if (inventory) {
486
+ // Release locked quantities from inventory
487
+ await tx
488
+ .getRepository(warehouse_base_1.Inventory)
489
+ .createQueryBuilder()
490
+ .update(warehouse_base_1.Inventory)
491
+ .set({
492
+ lockedQty: () => `COALESCE(locked_qty, 0) - ${deductQty}`,
493
+ lockedUomValue: () => `COALESCE(locked_uom_value, 0) - ${deductUomValue}`,
494
+ updater: user
495
+ })
496
+ .where('id = :id', { id: inventory.id })
497
+ .execute();
498
+ }
499
+ const newOiQty = oiReleaseQty - deductQty;
500
+ const newOiUomValue = oiReleaseUomValue - deductUomValue;
501
+ if (newOiQty <= 0 || newOiUomValue <= 0) {
502
+ // Mark this order inventory for deletion
503
+ orderInventoriesToDelete.push(oi.id);
504
+ }
505
+ else {
506
+ // Update order inventory with remaining quantity
507
+ await tx.getRepository(sales_base_1.OrderInventory).update(oi.id, {
508
+ releaseQty: newOiQty,
509
+ releaseUomValue: newOiUomValue,
510
+ updater: user
511
+ });
512
+ }
513
+ remainingReductionQty -= deductQty;
514
+ remainingReductionUomValue -= deductUomValue;
515
+ }
516
+ // Delete order inventories that are fully consumed
517
+ if (orderInventoriesToDelete.length > 0) {
518
+ // Find and delete worksheet details that reference these order inventories
519
+ const worksheetDetailsToDelete = await tx
520
+ .getRepository(worksheet_base_1.WorksheetDetail)
521
+ .createQueryBuilder('wsd')
522
+ .innerJoin('wsd.targetInventory', 'oi')
523
+ .where('oi.id IN (:...oiIds)', { oiIds: orderInventoriesToDelete })
524
+ .andWhere('wsd.domain = :domainId', { domainId: domain.id })
525
+ .getMany();
526
+ if (worksheetDetailsToDelete.length > 0) {
527
+ const worksheetDetailIds = worksheetDetailsToDelete.map(wsd => wsd.id);
528
+ await tx.getRepository(worksheet_base_1.WorksheetDetail).delete(worksheetDetailIds);
529
+ }
530
+ // Now delete the order inventories
531
+ await tx.getRepository(sales_base_1.OrderInventory).delete(orderInventoriesToDelete);
532
+ }
533
+ }
534
+ // Update order product totals
535
+ const remainingOrderInventories = (await tx.getRepository(sales_base_1.OrderInventory).find({
536
+ where: { orderProduct }
537
+ }));
538
+ const totalReleaseQty = remainingOrderInventories.reduce((sum, oi) => sum + (oi.releaseQty || 0), 0);
539
+ const totalReleaseUomValue = remainingOrderInventories.reduce((sum, oi) => sum + (oi.releaseUomValue || 0), 0);
540
+ await tx.getRepository(sales_base_1.OrderProduct).update(orderProduct.id, {
541
+ releaseQty: totalReleaseQty,
542
+ releaseUomValue: totalReleaseUomValue,
543
+ updater: user
544
+ });
545
+ }
546
+ async function assignAdditionalInventory(tx, orderProduct, qtyDiff, uomValueDiff, user, releaseGood, domain) {
547
+ var _a;
548
+ // Get customer bizplace from releaseGood (it's already loaded)
549
+ // If not loaded, fetch it using bizplaceId
550
+ let customerBizplace = releaseGood.bizplace;
551
+ if (!customerBizplace && releaseGood.bizplaceId) {
552
+ customerBizplace = await tx.getRepository(biz_base_1.Bizplace).findOne({
553
+ where: { id: releaseGood.bizplaceId }
554
+ });
555
+ }
556
+ if (!customerBizplace) {
557
+ throw new error_util_1.ApiError('E04', 'Customer bizplace not found for release order');
558
+ }
559
+ // Get release shelf life override from contact point (deliverTo)
560
+ let releaseShelfLifeOverride = null;
561
+ if (releaseGood.deliverToId) {
562
+ const deliverToContactPoint = await tx
563
+ .getRepository(biz_base_1.ContactPoint)
564
+ .findOne({ where: { id: releaseGood.deliverToId } });
565
+ if ((deliverToContactPoint === null || deliverToContactPoint === void 0 ? void 0 : deliverToContactPoint.releaseShelfLife) != null && deliverToContactPoint.releaseShelfLife !== 0) {
566
+ releaseShelfLifeOverride = deliverToContactPoint.releaseShelfLife;
567
+ }
568
+ }
569
+ // Get picking product setting
570
+ const pickingProductSetting = await tx.getRepository(setting_base_1.Setting).findOne({
571
+ where: { domain, name: 'rule-for-picking-product' }
572
+ });
573
+ // Determine orderInventory status based on releaseGood status
574
+ let orderInventoryStatus;
575
+ switch (releaseGood.status) {
576
+ case sales_base_1.ORDER_STATUS.PENDING:
577
+ orderInventoryStatus = sales_base_1.ORDER_INVENTORY_STATUS.PENDING;
578
+ break;
579
+ case sales_base_1.ORDER_STATUS.PENDING_RECEIVE:
580
+ orderInventoryStatus = sales_base_1.ORDER_INVENTORY_STATUS.PENDING_RECEIVE;
581
+ break;
582
+ case sales_base_1.ORDER_STATUS.PENDING_WORKSHEET:
583
+ orderInventoryStatus = sales_base_1.ORDER_INVENTORY_STATUS.PENDING_WORKSHEET;
584
+ break;
585
+ case sales_base_1.ORDER_STATUS.READY_TO_PICK:
586
+ orderInventoryStatus = sales_base_1.ORDER_INVENTORY_STATUS.READY_TO_PICK;
587
+ break;
588
+ default:
589
+ // Default to PENDING_WORKSHEET for other statuses
590
+ orderInventoryStatus = sales_base_1.ORDER_INVENTORY_STATUS.PENDING_WORKSHEET;
591
+ }
592
+ // Ensure product is loaded
593
+ if (!orderProduct.product) {
594
+ orderProduct.product = await tx.getRepository(product_base_1.Product).findOne({
595
+ where: { id: orderProduct.productId }
596
+ });
597
+ }
598
+ const product = orderProduct.product;
599
+ if (!product) {
600
+ throw new error_util_1.ApiError('E04', 'Product not found for order product');
601
+ }
602
+ // Determine picking strategy sortings
603
+ let sortings = [];
604
+ switch (product.pickingStrategy) {
605
+ case 'LIFO':
606
+ sortings.push({ name: 'iv.created_at', desc: true });
607
+ if (pickingProductSetting === null || pickingProductSetting === void 0 ? void 0 : pickingProductSetting.value) {
608
+ for (const [key, value] of Object.entries(JSON.parse(pickingProductSetting.value))) {
609
+ sortings.push({ name: `loc.${key}`, desc: value == 'DESC' ? true : false });
610
+ }
611
+ }
612
+ else {
613
+ sortings.push({ name: 'loc.name', desc: false }, { name: 'iv.created_at', desc: false });
614
+ }
615
+ break;
616
+ case 'FEFO':
617
+ sortings.push({ name: 'iv.expiration_date', desc: false }, { name: 'iv.created_at', desc: false });
618
+ if (pickingProductSetting === null || pickingProductSetting === void 0 ? void 0 : pickingProductSetting.value) {
619
+ for (const [key, value] of Object.entries(JSON.parse(pickingProductSetting.value))) {
620
+ sortings.push({ name: `loc.${key}`, desc: value == 'DESC' ? true : false });
621
+ }
622
+ }
623
+ else {
624
+ sortings.push({ name: 'loc.name', desc: false }, { name: 'iv.created_at', desc: false });
625
+ }
626
+ break;
627
+ case 'FMFO':
628
+ sortings.push({ name: 'iv.manufacture_date', desc: false }, { name: 'iv.created_at', desc: false });
629
+ if (pickingProductSetting === null || pickingProductSetting === void 0 ? void 0 : pickingProductSetting.value) {
630
+ for (const [key, value] of Object.entries(JSON.parse(pickingProductSetting.value))) {
631
+ sortings.push({ name: `loc.${key}`, desc: value == 'DESC' ? true : false });
632
+ }
633
+ }
634
+ else {
635
+ sortings.push({ name: 'loc.name', desc: false }, { name: 'iv.created_at', desc: false });
636
+ }
637
+ break;
638
+ case 'LOCATION':
639
+ if (pickingProductSetting === null || pickingProductSetting === void 0 ? void 0 : pickingProductSetting.value) {
640
+ for (const [key, value] of Object.entries(JSON.parse(pickingProductSetting.value))) {
641
+ sortings.push({ name: `loc.${key}`, desc: value == 'DESC' ? true : false });
642
+ }
643
+ }
644
+ else {
645
+ sortings.push({ name: 'loc.name', desc: false }, { name: 'iv.created_at', desc: false });
646
+ }
647
+ break;
648
+ // Every other case includes 'FIFO' will be applicable for this case
649
+ default:
650
+ sortings.push({ name: 'iv.created_at', desc: false });
651
+ if (pickingProductSetting === null || pickingProductSetting === void 0 ? void 0 : pickingProductSetting.value) {
652
+ for (const [key, value] of Object.entries(JSON.parse(pickingProductSetting.value))) {
653
+ sortings.push({ name: `loc.${key}`, desc: value == 'DESC' ? true : false });
654
+ }
655
+ }
656
+ else {
657
+ sortings.push({ name: 'loc.name', desc: false });
658
+ sortings.push({ name: 'iv.pallet_id', desc: false });
659
+ }
660
+ break;
661
+ }
662
+ let queryFilters = [];
663
+ let queryStrings = queryFilters.reduce((acc, itm, idx, arr) => {
664
+ acc.values.push(itm.filters);
665
+ switch (itm === null || itm === void 0 ? void 0 : itm.operator) {
666
+ case 'notin':
667
+ case 'in':
668
+ acc.query.push(`${itm.query} ${itm.operator == 'notin' ? 'NOT IN' : 'IN'} (${itm.filters
669
+ .map((itm, idx) => {
670
+ return `$${acc.values.length + 1}`;
671
+ })
672
+ .join(',')})`);
673
+ break;
674
+ default:
675
+ acc.query.push(`${itm.query} ${(itm === null || itm === void 0 ? void 0 : itm.operator) ? itm.operator : '='} $${acc.values.length + 1}`);
676
+ break;
677
+ }
678
+ acc.query.push(`${itm.query} ${(itm === null || itm === void 0 ? void 0 : itm.operator) ? itm.operator : '='} $${acc.values.length + 1}`);
679
+ return acc;
680
+ }, {
681
+ query: [],
682
+ values: []
683
+ });
684
+ let sortQuery = sortings
685
+ .map(itm => {
686
+ return `${itm.name} ${itm.desc ? 'DESC' : 'ASC'}`;
687
+ })
688
+ .join(`,`);
689
+ // Ensure productDetail is loaded for uom
690
+ if (!orderProduct.productDetail) {
691
+ orderProduct.productDetail = await tx.getRepository(product_base_1.ProductDetail).findOne({
692
+ where: { id: orderProduct.productDetailId }
693
+ });
694
+ }
695
+ const productDetail = orderProduct.productDetail;
696
+ const uom = (productDetail && productDetail.uom) || product.uom || 'UN';
697
+ let params = [
698
+ user.id,
699
+ domain.id,
700
+ customerBizplace.id,
701
+ orderProduct.packingType,
702
+ orderProduct.packingSize,
703
+ product.id,
704
+ warehouse_base_1.INVENTORY_STATUS.STORED,
705
+ warehouse_base_1.LOCATION_TYPE.QUARANTINE,
706
+ warehouse_base_1.LOCATION_TYPE.RESERVE,
707
+ warehouse_base_1.LOCATION_TYPE.STORAGE
708
+ ];
709
+ let batchId = orderProduct.batchId && orderProduct.batchId !== '-' ? String(orderProduct.batchId).trim() : null;
710
+ if (batchId)
711
+ params.push(batchId);
712
+ params = [...params, ...queryStrings.values];
713
+ // Handle warehouse code filtering
714
+ const warehouseCode = orderProduct.warehouseCode;
715
+ const warehouseNameFilter = warehouseCode ? `AND w.name = $${params.length + 1}` : '';
716
+ if (warehouseCode) {
717
+ params.push(String(warehouseCode).trim());
718
+ }
719
+ // Add release shelf life override parameter
720
+ const releaseShelfLifeParamIndex = params.length + 1;
721
+ params.push(releaseShelfLifeOverride);
722
+ // Build the sophisticated SQL query with window functions
723
+ let query = `
724
+ update inventories tgt set locked_qty = coalesce(locked_qty,0) + src.reserve_qty,
725
+ locked_uom_value = coalesce(locked_uom_value,0) + src.reserve_uom_value,
726
+ updated_at = NOW(),
727
+ updater_id = $1
728
+ from (
729
+ select "id", "pallet_id","carton_id", "batch_id", "batch_id_ref", "unit", "uom", "packing_type", "packing_size", "manufacture_year",
730
+ "reserve_qty", (("uom_value"/"qty") * "reserve_qty") as "reserve_uom_value" from (
731
+ select "sort_seq", "id", "pallet_id", "batch_id", "batch_id_ref", "unit", "uom", "packing_type", "packing_size", "manufacture_year", "carton_id", "uom_value", "locked_uom_value", "qty", "locked_qty", "created_at",
732
+ "release_qty", "release_uom_value", lock_amount,
733
+ case when "lock_amount" < 0 then "available_qty" else "available_qty" - "lock_amount" end as "reserve_qty"
734
+ from (
735
+ select *,
736
+ (
737
+ case when (qty - greatest(locked_qty, 0) - greatest(unassigned_qty, 0)) < 0 then 0
738
+ else qty - greatest(locked_qty, 0) - greatest(unassigned_qty, 0) end
739
+ ) as available_qty,
740
+ sum(qty - locked_qty - release_qty - unassigned_qty) over (order by sort_seq asc rows between unbounded preceding and current row) as lock_amount
741
+ from (
742
+ SELECT 0 as sort_seq, null as id, null as pallet_id, null as batch_id, null as batch_id_ref,
743
+ null as unit, '${uom}' as uom, '${orderProduct.packingType}' as packing_type, '${orderProduct.packingSize}' as packing_size,
744
+ null as manufacture_year, null as carton_id, 0 as uom_value, 0 as locked_uom_value, 0 as qty, 0 as locked_qty, 0 as unassigned_uom_value, 0 as unassigned_qty, null as created_at,
745
+ ${qtyDiff}::numeric as release_qty, ${uomValueDiff}::numeric as release_uom_value
746
+ UNION
747
+ (
748
+ SELECT ROW_NUMBER() OVER(PARTITION BY iv.domain_id ORDER BY wiar.rank ${sortQuery ? ', ' + sortQuery : ''}) as sort_seq,
749
+ iv.id, iv.pallet_id, iv.batch_id, iv.batch_id_ref,
750
+ iv.unit, iv.uom, iv.packing_type, iv.packing_size,
751
+ iv.manufacture_year, iv.carton_id, iv.uom_value,
752
+ coalesce(iv.locked_uom_value,0) as locked_uom_value, iv.qty, greatest(coalesce(iv.locked_qty,0),0) as locked_qty, coalesce(pds.unassigned_uom_value,0) as unassigned_uom_value, greatest(coalesce(pds.unassigned_qty,0),0) as unassigned_qty,
753
+ iv.created_at, 0 as release_qty, 0 as release_uom_value
754
+ FROM "inventories" "iv"
755
+ LEFT JOIN "product_detail_stocks" "pds" ON "pds"."product_detail_id" = "iv"."product_detail_id"
756
+ INNER JOIN "locations" "loc" ON "loc"."id"="iv"."location_id"
757
+ INNER JOIN "warehouses" "w" ON "w"."id" = "loc"."warehouse_id"
758
+ INNER JOIN "products" "p" ON "p"."id"="iv"."product_id"
759
+ INNER JOIN "warehouse_inventory_assignment_rankings" "wiar" ON "wiar"."location_type"="loc"."type"
760
+ WHERE case when coalesce("iv"."locked_qty",0) < 0 then 0 else ("iv"."qty" - coalesce("iv"."locked_qty",0)) end > 0 AND
761
+ "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)
762
+ AND ${batchId ? `"iv"."batch_id" = $11` : `1=1`}
763
+ ${warehouseNameFilter}
764
+ AND "iv"."obsolete" = false AND (
765
+ "iv"."expiration_date" IS NULL
766
+ OR
767
+ CASE
768
+ WHEN $${releaseShelfLifeParamIndex}::integer IS NOT NULL AND $${releaseShelfLifeParamIndex}::integer > 0 THEN
769
+ CURRENT_DATE < ("iv"."expiration_date" - $${releaseShelfLifeParamIndex}::integer)
770
+ WHEN "p"."min_outbound_shelf_life" IS NOT NULL AND "p"."min_outbound_shelf_life" > 0 THEN
771
+ CURRENT_DATE < ("iv"."expiration_date" - "p"."min_outbound_shelf_life")
772
+ ELSE
773
+ TRUE
774
+ END
775
+ )
776
+ ${queryStrings.query.length > 0 ? `AND ${queryStrings.query.join(' AND ')}` : ''}
777
+ ORDER BY wiar.rank ${sortQuery ? ', ' + sortQuery : ''}
778
+ )
779
+ ) dt1
780
+ ) dt2 where case when "lock_amount" < 0 then "available_qty" else "available_qty" - "lock_amount" end > 0
781
+ ) dt3 where sort_seq > 0
782
+ ) src where src.id = tgt.id
783
+ returning
784
+ tgt."id", tgt."qty", tgt."pallet_id", tgt."carton_id", tgt."batch_id", tgt."batch_id_ref", tgt."unit",
785
+ tgt."uom", tgt."packing_type", tgt."packing_size", tgt."manufacture_year",
786
+ tgt."locked_qty", tgt."uom_value", tgt."locked_uom_value", src."reserve_qty", src."reserve_uom_value";`;
787
+ let updatedInventories = await tx.getRepository(warehouse_base_1.Inventory).query(query, params);
788
+ let totalAssigned = ((_a = updatedInventories[0]) === null || _a === void 0 ? void 0 : _a.reduce((acc, inv) => {
789
+ return acc + inv.reserve_qty;
790
+ }, 0)) || 0;
791
+ // For non-decimal products, round the values before comparison
792
+ let roundedQtyDiff = qtyDiff;
793
+ if (!product.isInventoryDecimal) {
794
+ roundedQtyDiff = Math.round(qtyDiff);
795
+ totalAssigned = Math.round(totalAssigned);
796
+ }
797
+ else {
798
+ // For decimal products, round to 3 decimal places
799
+ roundedQtyDiff = Math.round(qtyDiff * 1000) / 1000;
800
+ totalAssigned = Math.round(totalAssigned * 1000) / 1000;
801
+ }
802
+ if (Math.abs(roundedQtyDiff - totalAssigned) > 0.001) {
803
+ // Using small epsilon for float comparison
804
+ throw new error_util_1.ApiError('E01', 'INSUFFICIENT_STOCK');
805
+ }
806
+ // Create order inventory records for each assigned inventory
807
+ if (updatedInventories[0] && updatedInventories[0].length > 0) {
808
+ // Ensure releaseGood.id is available (required for TypeORM relation)
809
+ if (!releaseGood.id) {
810
+ throw new error_util_1.ApiError('E04', 'Release good ID is required for order inventory');
811
+ }
812
+ // Ensure orderProduct.id is available (required for TypeORM relation)
813
+ if (!orderProduct.id) {
814
+ throw new error_util_1.ApiError('E04', 'Order product ID is required for order inventory');
815
+ }
816
+ // Get existing order inventories for this order product to check for duplicates
817
+ const existingOrderInventories = await tx.getRepository(sales_base_1.OrderInventory).find({
818
+ where: { orderProduct },
819
+ relations: ['inventory']
820
+ });
821
+ // Check if worksheets exist for this release good
822
+ const existingWorksheets = await tx.getRepository(worksheet_base_1.Worksheet).find({
823
+ where: {
824
+ releaseGood: { id: releaseGood.id },
825
+ type: worksheet_base_1.WORKSHEET_TYPE.PICKING,
826
+ status: (0, typeorm_1.In)([worksheet_base_1.WORKSHEET_STATUS.DEACTIVATED])
827
+ },
828
+ relations: ['bizplace']
829
+ });
830
+ const newOrderInventories = [];
831
+ for (const inv of updatedInventories[0]) {
832
+ // Check if an order inventory already exists for this inventory ID
833
+ const existingOI = existingOrderInventories.find((oi) => oi.inventory && oi.inventory.id === inv.id);
834
+ let savedOrderInventory;
835
+ if (existingOI) {
836
+ // Update existing order inventory
837
+ const updatedReleaseQty = (existingOI.releaseQty || 0) + inv.reserve_qty;
838
+ const updatedReleaseUomValue = (existingOI.releaseUomValue || 0) + inv.reserve_uom_value;
839
+ await tx.getRepository(sales_base_1.OrderInventory).update(existingOI.id, {
840
+ releaseQty: updatedReleaseQty,
841
+ releaseUomValue: updatedReleaseUomValue,
842
+ updater: user
843
+ });
844
+ // Reload to get updated entity
845
+ savedOrderInventory = (await tx.getRepository(sales_base_1.OrderInventory).findOne({
846
+ where: { id: existingOI.id },
847
+ relations: ['inventory']
848
+ }));
849
+ }
850
+ else {
851
+ // Create new order inventory
852
+ savedOrderInventory = await tx.getRepository(sales_base_1.OrderInventory).save(Object.assign(new sales_base_1.OrderInventory(), {
853
+ name: (0, uuid_1.v4)(),
854
+ domain: domain,
855
+ bizplace: customerBizplace,
856
+ releaseGood: releaseGood,
857
+ releaseGoodId: releaseGood.id,
858
+ orderProduct: orderProduct,
859
+ orderProductId: orderProduct.id,
860
+ product: product,
861
+ productDetail: productDetail,
862
+ inventory: { id: inv.id },
863
+ packingType: inv.packing_type,
864
+ packingSize: inv.packing_size,
865
+ batchId: inv.batch_id,
866
+ releaseQty: inv.reserve_qty,
867
+ releaseUomValue: inv.reserve_uom_value,
868
+ uom: inv.uom,
869
+ type: sales_base_1.ORDER_TYPES.RELEASE_OF_GOODS,
870
+ status: orderInventoryStatus,
871
+ creator: user,
872
+ updater: user
873
+ }));
874
+ newOrderInventories.push(savedOrderInventory);
875
+ }
876
+ }
877
+ // Create worksheet details for newly created order inventories if worksheets exist
878
+ if (existingWorksheets.length > 0 && newOrderInventories.length > 0) {
879
+ for (const worksheet of existingWorksheets) {
880
+ const worksheetDetails = newOrderInventories.map((oi) => ({
881
+ domain: domain,
882
+ bizplace: worksheet.bizplace || customerBizplace,
883
+ worksheet: worksheet,
884
+ name: worksheet_base_1.WorksheetNoGenerator.pickingDetail(),
885
+ type: worksheet_base_1.WORKSHEET_TYPE.PICKING,
886
+ status: worksheet_base_1.WORKSHEET_STATUS.DEACTIVATED,
887
+ targetInventory: oi,
888
+ creator: user,
889
+ updater: user
890
+ }));
891
+ await tx.getRepository(worksheet_base_1.WorksheetDetail).save(worksheetDetails);
892
+ }
893
+ }
894
+ }
895
+ }
896
+ async function addNewOrderProduct(tx, orderProductReq, releaseGood, domain, user, context, hasOrderInventories) {
897
+ var _a, _b, _c;
898
+ const sku = ((_a = orderProductReq.product) === null || _a === void 0 ? void 0 : _a.sku) || orderProductReq.sku;
899
+ const refCode = ((_b = orderProductReq.product) === null || _b === void 0 ? void 0 : _b.refCode) || orderProductReq.refCode;
900
+ const packingType = orderProductReq.packingType;
901
+ const packingSize = orderProductReq.packingSize;
902
+ const releaseQty = orderProductReq.releaseQty !== undefined ? orderProductReq.releaseQty : orderProductReq.qty;
903
+ const refItemId = ((_c = orderProductReq.product) === null || _c === void 0 ? void 0 : _c.refItemId) || orderProductReq.refItemId || null;
904
+ if (!sku && !refCode) {
905
+ throw new error_util_1.ApiError('E01', 'sku or refCode is required for new items');
906
+ }
907
+ if (!releaseQty || releaseQty <= 0) {
908
+ throw new error_util_1.ApiError('E01', 'releaseQty must be greater than 0');
909
+ }
910
+ // Ensure releaseGood has an ID (required for TypeORM relation)
911
+ if (!releaseGood.id) {
912
+ throw new error_util_1.ApiError('E04', 'Release good ID is required');
913
+ }
914
+ // Get company bizplace for product lookup
915
+ const customerCompanyBizplace = await (0, biz_base_1.getCompanyBizplace)(null, null, releaseGood.bizplaceId, tx);
916
+ // Find product detail
917
+ const qb = tx
918
+ .getRepository(product_base_1.ProductDetail)
919
+ .createQueryBuilder('pd')
920
+ .innerJoinAndSelect('pd.product', 'prod')
921
+ .where('prod.bizplace_id = :bizplaceId', { bizplaceId: customerCompanyBizplace.id })
922
+ .andWhere('pd.deleted_at IS NULL');
923
+ if (refCode) {
924
+ qb.andWhere('pd.ref_code = :refCode', { refCode });
925
+ }
926
+ if (sku) {
927
+ qb.andWhere('prod.sku = :sku', { sku });
928
+ }
929
+ if (packingType) {
930
+ qb.andWhere('pd.packing_type = :packingType', { packingType });
931
+ }
932
+ if (packingSize) {
933
+ qb.andWhere('pd.packing_size = :packingSize', { packingSize });
934
+ }
935
+ if (!refCode && !packingType && !packingSize) {
936
+ qb.andWhere('pd.is_default = :isDefault', { isDefault: true });
937
+ }
938
+ const productDetail = await qb.getOne();
939
+ if (!productDetail) {
940
+ throw new error_util_1.ApiError('E04', `Product not found: ${sku || refCode}`);
941
+ }
942
+ // Validate decimal places
943
+ if (!productDetail.product.isInventoryDecimal && releaseQty % 1 !== 0) {
944
+ throw new error_util_1.ApiError('E01', `releaseQty must be an integer for product ${sku || refCode}`);
945
+ }
946
+ // Round to 3 decimal places
947
+ const roundedReleaseQty = productDetail.product.isInventoryDecimal
948
+ ? Math.round(releaseQty * 1000) / 1000
949
+ : Math.round(releaseQty);
950
+ const releaseUomValue = roundedReleaseQty * (productDetail.uomValue || 1);
951
+ // Ensure bizplace is loaded - fetch it if not already loaded
952
+ let bizplace = releaseGood.bizplace;
953
+ if (!bizplace && releaseGood.bizplaceId) {
954
+ bizplace = await tx.getRepository(biz_base_1.Bizplace).findOne({ where: { id: releaseGood.bizplaceId } });
955
+ if (!bizplace) {
956
+ throw new error_util_1.ApiError('E04', 'Bizplace not found for release order');
957
+ }
958
+ }
959
+ else if (!bizplace) {
960
+ throw new error_util_1.ApiError('E04', 'Bizplace is required for order product');
961
+ }
962
+ const repo = tx.getRepository(sales_base_1.OrderProduct);
963
+ const created = repo.create({
964
+ name: `OP-` + (0, uuid_1.v4)(),
965
+ // Use relation stubs (IDs only) to force FK persistence
966
+ domain: { id: domain.id },
967
+ bizplace: { id: bizplace.id },
968
+ releaseGood: { id: releaseGood.id },
969
+ releaseGoodId: releaseGood.id,
970
+ product: { id: productDetail.product.id },
971
+ productDetail: { id: productDetail.id },
972
+ packingType: productDetail.packingType,
973
+ packingSize: packingSize || productDetail.packingSize,
974
+ batchId: orderProductReq.batchId || '',
975
+ refItemId: refItemId,
976
+ releaseQty: roundedReleaseQty,
977
+ releaseUomValue: releaseUomValue,
978
+ uom: productDetail.uom,
979
+ uomValue: productDetail.uomValue,
980
+ packQty: 0,
981
+ actualPackQty: 0,
982
+ palletQty: 0,
983
+ actualPalletQty: 0,
984
+ type: sales_base_1.ORDER_TYPES.RELEASE_OF_GOODS,
985
+ status: sales_base_1.ORDER_PRODUCT_STATUS.ASSIGNED,
986
+ unitPrice: orderProductReq.unitPrice || null,
987
+ remark: orderProductReq.remark || null,
988
+ creator: user,
989
+ updater: user
990
+ });
991
+ const newOrderProduct = await repo.save(created);
992
+ if (hasOrderInventories) {
993
+ // Assign inventory - pass releaseGood so status can be determined
994
+ await assignAdditionalInventory(tx, newOrderProduct, roundedReleaseQty, releaseUomValue, user, releaseGood, domain);
995
+ }
996
+ else {
997
+ // Update ProductDetailStock
998
+ await tx
999
+ .getRepository(warehouse_base_1.ProductDetailStock)
1000
+ .createQueryBuilder()
1001
+ .update(warehouse_base_1.ProductDetailStock)
1002
+ .set({
1003
+ unassignedQty: () => `"unassigned_qty" + ${roundedReleaseQty}`,
1004
+ unassignedUomValue: () => `"unassigned_uom_value" + ${releaseUomValue}`
1005
+ })
1006
+ .where({ productDetail: productDetail.id })
1007
+ .execute();
1008
+ }
82
1009
  }
83
1010
  //# sourceMappingURL=update-release-order-details.js.map