@things-factory/worksheet-base 4.3.248 → 4.3.250

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.
@@ -7,6 +7,7 @@ import { generateId } from '@things-factory/id-rule-base'
7
7
  import { Powrup } from '@things-factory/integration-powrup'
8
8
  import { Sellercraft, SellercraftStatus } from '@things-factory/integration-sellercraft'
9
9
  import { Product, ProductBarcode, ProductDetail } from '@things-factory/product-base'
10
+
10
11
  import {
11
12
  GenerateBatchPickInfo,
12
13
  ORDER_INVENTORY_STATUS,
@@ -20,6 +21,8 @@ import {
20
21
  OrderToteSeal,
21
22
  OrderVas,
22
23
  ReleaseGood,
24
+ InventoryUtil,
25
+ ORDER_TYPES,
23
26
  PowrupController
24
27
  } from '@things-factory/sales-base'
25
28
  import { Setting } from '@things-factory/setting-base'
@@ -34,13 +37,14 @@ import {
34
37
  Location,
35
38
  LOCATION_TYPE,
36
39
  Tote,
37
- TOTE_STATUS
40
+ TOTE_STATUS,
41
+ ProductDetailStock
38
42
  } from '@things-factory/warehouse-base'
39
43
 
40
44
  import { TASK_NUMBER_RULE_TYPE, TASK_NUMBER_SETTING_KEY, WORKSHEET_STATUS, WORKSHEET_TYPE } from '../../constants'
41
45
  import { SellercraftController } from '../../controllers'
42
46
  import { Worksheet, WorksheetDetail } from '../../entities'
43
- import { isInventoryObsolete, WorksheetNoGenerator } from '../../utils'
47
+ import { isInventoryObsolete, WorksheetNoGenerator, inventoriesByStrategy } from '../../utils'
44
48
  import { VasWorksheetController } from '../vas/vas-worksheet-controller'
45
49
 
46
50
  export class PickingWorksheetController extends VasWorksheetController {
@@ -113,6 +117,10 @@ export class PickingWorksheetController extends VasWorksheetController {
113
117
  })
114
118
  })
115
119
 
120
+ const worksheetPickingAssignment: Setting = await this.trxMgr.getRepository(Setting).findOne({
121
+ where: { domain: this.domain, category: 'id-rule', name: 'enable-worksheet-picking-activation-assignment' }
122
+ })
123
+
116
124
  const worksheetSetting: Setting = await this.trxMgr.getRepository(Setting).findOne({
117
125
  where: {
118
126
  domain: this.domain,
@@ -136,24 +144,42 @@ export class PickingWorksheetController extends VasWorksheetController {
136
144
  worksheet.updater = this.user
137
145
  worksheet = await this.trxMgr.getRepository(Worksheet).save(worksheet)
138
146
 
139
- if (orderInventories.some((oi: OrderInventory) => oi.inventory?.id)) {
140
- const hasInventoryOIs: OrderInventory[] = orderInventories.filter(
141
- (orderInventory: OrderInventory) => orderInventory.inventory !== null
142
- )
147
+ if (orderInventories?.length > 0) {
148
+ if (orderInventories.some((oi: OrderInventory) => oi.inventory?.id)) {
149
+ const hasInventoryOIs: OrderInventory[] = orderInventories.filter(
150
+ (orderInventory: OrderInventory) => orderInventory.inventory !== null
151
+ )
143
152
 
144
- worksheet.worksheetDetails = await this.createWorksheetDetails(
145
- worksheet,
146
- WORKSHEET_TYPE.BATCH_PICKING,
147
- hasInventoryOIs
148
- )
149
- }
153
+ worksheet.worksheetDetails = await this.createWorksheetDetails(
154
+ worksheet,
155
+ WORKSHEET_TYPE.BATCH_PICKING,
156
+ hasInventoryOIs
157
+ )
158
+ }
150
159
 
151
- orderInventories.forEach((oi: OrderInventory) => {
152
- oi.refWorksheetId = worksheet.id
153
- oi.status = oi.inventory?.id ? ORDER_INVENTORY_STATUS.READY_TO_PICK : ORDER_INVENTORY_STATUS.PENDING_SPLIT
154
- oi.updater = this.user
155
- })
156
- await this.updateOrderTargets(orderInventories)
160
+ orderInventories.forEach((oi: OrderInventory) => {
161
+ oi.refWorksheetId = worksheet.id
162
+ oi.status = oi.inventory?.id ? ORDER_INVENTORY_STATUS.READY_TO_PICK : ORDER_INVENTORY_STATUS.PENDING_SPLIT
163
+ oi.updater = this.user
164
+ })
165
+
166
+ await this.updateOrderTargets(orderInventories)
167
+ } else {
168
+ let orderProducts: OrderProduct[] = []
169
+ releaseGoods.map((releaseGood: ReleaseGood) => {
170
+ const foundOPs: OrderProduct[] = releaseGood.orderProducts
171
+ foundOPs.map((op: OrderProduct) => {
172
+ orderProducts.push(op)
173
+ })
174
+ })
175
+ if (worksheetPickingAssignment && orderProducts?.length > 0) {
176
+ worksheet.worksheetDetails = await this.createWorksheetDetails(
177
+ worksheet,
178
+ WORKSHEET_TYPE.BATCH_PICKING,
179
+ orderProducts
180
+ )
181
+ }
182
+ }
157
183
 
158
184
  releaseGoods = releaseGoods.map((releaseGood: ReleaseGood) => {
159
185
  return {
@@ -192,6 +218,9 @@ export class PickingWorksheetController extends VasWorksheetController {
192
218
  'releaseGood.domain',
193
219
  'releaseGood.lastMileDelivery',
194
220
  'releaseGood.bizplace.domain',
221
+ 'releaseGood.orderProducts',
222
+ 'releaseGood.orderProducts.productDetail',
223
+ 'releaseGood.orderProducts.productDetail.product',
195
224
  'releaseGood.orderPackages',
196
225
  'releaseGood.orderPackages.orderPackageItems',
197
226
  'releaseGood.orderPackages.orderPackageItems.orderProduct',
@@ -206,17 +235,25 @@ export class PickingWorksheetController extends VasWorksheetController {
206
235
  'worksheetDetails.targetInventory.product'
207
236
  ])
208
237
 
209
- const worksheetDetails: WorksheetDetail[] = worksheet.worksheetDetails.filter(x => x.status == 'DEACTIVATED')
210
- const targetInventories: OrderInventory[] = worksheetDetails.map((wsd: WorksheetDetail) => {
211
- let targetInventory: OrderInventory = wsd.targetInventory
212
- targetInventory.status = ORDER_INVENTORY_STATUS.PICKING
213
- targetInventory.updater = this.user
238
+ let worksheetDetails: WorksheetDetail[]
239
+ let releaseGood: ReleaseGood = worksheet.releaseGood
214
240
 
215
- return targetInventory
216
- })
217
- this.updateOrderTargets(targetInventories)
241
+ // assign inventory if unassigned
242
+ if (!releaseGood.assignedInventory) {
243
+ worksheetDetails = await this.assignInventoriesForUnassignedOrder(worksheet)
244
+ worksheet.worksheetDetails = worksheetDetails
245
+ releaseGood.assignedInventory = true
246
+ } else {
247
+ worksheetDetails = worksheet.worksheetDetails.filter(x => x.status == 'DEACTIVATED')
248
+ const targetInventories: OrderInventory[] = worksheetDetails.map((wsd: WorksheetDetail) => {
249
+ let targetInventory: OrderInventory = wsd.targetInventory
250
+ targetInventory.status = ORDER_INVENTORY_STATUS.PICKING
251
+ targetInventory.updater = this.user
218
252
 
219
- let releaseGood: ReleaseGood = worksheet.releaseGood
253
+ return targetInventory
254
+ })
255
+ this.updateOrderTargets(targetInventories)
256
+ }
220
257
  releaseGood.status = ORDER_STATUS.PICKING
221
258
  releaseGood.updater = this.user
222
259
  this.updateRefOrder(releaseGood)
@@ -311,6 +348,11 @@ export class PickingWorksheetController extends VasWorksheetController {
311
348
  async activateBatchPicking(worksheetNo: string): Promise<Worksheet> {
312
349
  let worksheet: Worksheet = await this.findActivatableWorksheet(worksheetNo, WORKSHEET_TYPE.BATCH_PICKING, [
313
350
  'worksheetDetails',
351
+ 'worksheetDetails.targetProduct',
352
+ 'worksheetDetails.targetProduct.bizplace',
353
+ 'worksheetDetails.targetProduct.product',
354
+ 'worksheetDetails.targetProduct.productDetail',
355
+ 'worksheetDetails.targetProduct.releaseGood',
314
356
  'worksheetDetails.targetInventory',
315
357
  'worksheetDetails.targetInventory.releaseGood',
316
358
  'worksheetDetails.targetInventory.releaseGood.domain',
@@ -321,15 +363,21 @@ export class PickingWorksheetController extends VasWorksheetController {
321
363
  'bizplace.company.domain'
322
364
  ])
323
365
 
324
- const worksheetDetails: WorksheetDetail[] = worksheet.worksheetDetails.filter(x => x.status == 'DEACTIVATED')
325
- const targetInventories: OrderInventory[] = worksheetDetails.map((wsd: WorksheetDetail) => {
326
- let targetInventory: OrderInventory = wsd.targetInventory
327
- targetInventory.status = ORDER_INVENTORY_STATUS.PICKING
328
- targetInventory.updater = this.user
366
+ let worksheetDetails: WorksheetDetail[] = worksheet.worksheetDetails.filter(x => x.status == 'DEACTIVATED')
329
367
 
330
- return targetInventory
331
- })
332
- this.updateOrderTargets(targetInventories)
368
+ if (worksheet.worksheetDetails.find(wsd => wsd.targetProduct?.releaseGood?.assignedInventory == false)) {
369
+ worksheetDetails = await this.assignInventoriesForUnassignedMergedOrder(worksheet)
370
+ worksheet.worksheetDetails = worksheetDetails
371
+ } else {
372
+ const targetInventories: OrderInventory[] = worksheetDetails.map((wsd: WorksheetDetail) => {
373
+ let targetInventory: OrderInventory = wsd.targetInventory
374
+ targetInventory.status = ORDER_INVENTORY_STATUS.PICKING
375
+ targetInventory.updater = this.user
376
+
377
+ return targetInventory
378
+ })
379
+ this.updateOrderTargets(targetInventories)
380
+ }
333
381
 
334
382
  worksheet = await this.activateWorksheet(worksheet, worksheetDetails, [])
335
383
 
@@ -419,7 +467,8 @@ export class PickingWorksheetController extends VasWorksheetController {
419
467
  return {
420
468
  ...releaseGood,
421
469
  status: ORDER_STATUS.PICKING,
422
- updater: this.user
470
+ updater: this.user,
471
+ assignedInventory: true
423
472
  }
424
473
  })
425
474
  )
@@ -1970,4 +2019,247 @@ export class PickingWorksheetController extends VasWorksheetController {
1970
2019
  })
1971
2020
  }
1972
2021
  }
2022
+
2023
+ async assignInventoriesForUnassignedOrder(worksheet: Worksheet): Promise<any> {
2024
+ const releaseGood = worksheet.releaseGood
2025
+ const orderProducts = releaseGood.orderProducts
2026
+ let finalOrderInventories: OrderInventory[] = []
2027
+ let newWorksheetDetails: WorksheetDetail[] = []
2028
+ const inventoryAssignmentSetting: Setting = await this.trxMgr.getRepository(Setting).findOne({
2029
+ where: { domain: this.domain, name: 'rule-for-inventory-assignment' }
2030
+ })
2031
+
2032
+ for (let op of orderProducts) {
2033
+ let locationSortingRules = []
2034
+ if (inventoryAssignmentSetting) {
2035
+ let locationSetting = JSON.parse(inventoryAssignmentSetting.value)
2036
+ for (const key in locationSetting) {
2037
+ locationSortingRules.push({ name: key, desc: locationSetting[key] == 'ASC' ? false : true })
2038
+ }
2039
+ }
2040
+
2041
+ let assignedOrderInventories: OrderInventory[] = await InventoryUtil.autoAssignInventoryForRelease(
2042
+ op,
2043
+ op.productDetail.product,
2044
+ op.productDetail,
2045
+ locationSortingRules,
2046
+ releaseGood.bizplace,
2047
+ this.domain,
2048
+ this.trxMgr,
2049
+ op.batchId
2050
+ )
2051
+
2052
+ assignedOrderInventories = assignedOrderInventories.map(aoi => {
2053
+ delete aoi.id
2054
+ return {
2055
+ ...aoi,
2056
+ orderProduct: op,
2057
+ status: ORDER_INVENTORY_STATUS.PICKING,
2058
+ name: OrderNoGenerator.orderInventory(),
2059
+ domain: this.domain,
2060
+ bizplace: { id: releaseGood.bizplace.id },
2061
+ releaseGood
2062
+ }
2063
+ })
2064
+
2065
+ finalOrderInventories.push(...assignedOrderInventories)
2066
+
2067
+ await this.trxMgr
2068
+ .getRepository(OrderProduct)
2069
+ .update({ id: op.id }, { status: ORDER_PRODUCT_STATUS.ASSIGNED, updater: this.user })
2070
+ }
2071
+
2072
+ for (let oi of finalOrderInventories) {
2073
+ // update inventory locked qty and uom value
2074
+ oi = await this.trxMgr.getRepository(OrderInventory).save({ ...oi })
2075
+
2076
+ await this.trxMgr.getRepository(Inventory).update(oi.inventory.id, {
2077
+ lockedQty: (oi.inventory?.lockedQty || 0) + oi.releaseQty,
2078
+ lockedUomValue: (oi.inventory?.lockedUomValue || 0) + oi.releaseUomValue,
2079
+ updater: this.user
2080
+ })
2081
+
2082
+ // update product detail stock deduct unassigned qty and unassigned uom value
2083
+ await this.trxMgr
2084
+ .getRepository(ProductDetailStock)
2085
+ .createQueryBuilder()
2086
+ .update(ProductDetailStock)
2087
+ .set({
2088
+ unassignedQty: () => `"unassigned_qty" - ${oi.releaseQty}`,
2089
+ unassignedUomValue: () => `"unassigned_uom_value" - ${oi.releaseUomValue}`
2090
+ })
2091
+ .where({ productDetail: oi.productDetail.id })
2092
+ .execute()
2093
+
2094
+ const worksheetDetail: WorksheetDetail = Object.assign(new WorksheetDetail(), {
2095
+ domain: this.domain,
2096
+ bizplace: { id: releaseGood.bizplace.id },
2097
+ worksheet,
2098
+ name: WorksheetNoGenerator.pickingDetail(),
2099
+ seq: 0,
2100
+ targetInventory: oi,
2101
+ targetProduct: oi.orderProduct,
2102
+ type: WORKSHEET_TYPE.PICKING,
2103
+ status: WORKSHEET_STATUS.DEACTIVATED,
2104
+ creator: this.user,
2105
+ updater: this.user
2106
+ })
2107
+
2108
+ newWorksheetDetails.push(worksheetDetail)
2109
+ }
2110
+ newWorksheetDetails = await this.trxMgr.getRepository(WorksheetDetail).save(newWorksheetDetails)
2111
+
2112
+ return newWorksheetDetails
2113
+ }
2114
+
2115
+ async assignInventoriesForUnassignedMergedOrder(worksheet: Worksheet): Promise<any> {
2116
+ //const releaseGood = worksheet.releaseGood
2117
+ const orderProducts = worksheet.worksheetDetails
2118
+ .filter(itm => itm.targetProduct.releaseGood.assignedInventory == false)
2119
+ .map(wsd => wsd.targetProduct)
2120
+ const releaseGoodIds = [...new Set(orderProducts.map(op => op.releaseGood.id))]
2121
+ let newWorksheetDetails: WorksheetDetail[] = []
2122
+
2123
+ const inventoryAssignmentSetting: Setting = await this.trxMgr.getRepository(Setting).findOne({
2124
+ where: { domain: this.domain, name: 'rule-for-inventory-assignment' }
2125
+ })
2126
+
2127
+ for (let i = 0; i < orderProducts.length; i++) {
2128
+ let pInventoryList = inventoriesByStrategy(
2129
+ {
2130
+ ...orderProducts[i],
2131
+ worksheetId: worksheet.id,
2132
+ bizplaceId: worksheet.bizplace.id,
2133
+ locationSortingRules: inventoryAssignmentSetting ? JSON.parse(inventoryAssignmentSetting.value) : false,
2134
+ pickingStrategy: orderProducts[i].product.pickingStrategy
2135
+ },
2136
+ this.domain,
2137
+ this.trxMgr
2138
+ )
2139
+
2140
+ let idx = 0
2141
+ let [inventoryList] = await Promise.all([pInventoryList])
2142
+
2143
+ let { releaseQty, releaseUomValue } = orderProducts[i]
2144
+
2145
+ while (releaseQty > 0 && idx < inventoryList.items.length) {
2146
+ let targetInventory: Inventory = await this.trxMgr
2147
+ .getRepository(Inventory)
2148
+ .findOne({ where: { id: inventoryList.items[idx].id } })
2149
+
2150
+ let inventoryAvailableQty = targetInventory.qty - (targetInventory?.lockedQty || 0)
2151
+ let inventoryAvailableUomValue = targetInventory.uomValue - (targetInventory?.lockedUomValue || 0)
2152
+
2153
+ if (inventoryAvailableQty > 0) {
2154
+ let allocatedQty = 0,
2155
+ allocatedUomValue = 0
2156
+ if (inventoryAvailableQty < releaseQty) {
2157
+ idx = idx + 1
2158
+ allocatedQty = inventoryAvailableQty
2159
+ allocatedUomValue = inventoryAvailableUomValue
2160
+ } else {
2161
+ allocatedQty = releaseQty
2162
+ allocatedUomValue = releaseUomValue
2163
+ }
2164
+
2165
+ releaseQty = releaseQty - allocatedQty
2166
+ releaseUomValue = releaseUomValue - allocatedUomValue
2167
+
2168
+ //// Update inventory locked quantity
2169
+ await this.trxMgr
2170
+ .getRepository(Inventory)
2171
+ .createQueryBuilder('inv')
2172
+ .update(Inventory)
2173
+ .set({
2174
+ lockedUomValue: () => `COALESCE(locked_uom_value,0) + ${allocatedUomValue}`,
2175
+ lockedQty: () => `COALESCE(locked_qty,0) + ${allocatedQty}`
2176
+ })
2177
+ .where('id = :id', { id: targetInventory.id })
2178
+ .execute()
2179
+
2180
+ // update product detail stock deduct unassigned qty and unassigned uom value
2181
+ await this.trxMgr
2182
+ .getRepository(ProductDetailStock)
2183
+ .createQueryBuilder()
2184
+ .update(ProductDetailStock)
2185
+ .set({
2186
+ unassignedQty: () => `"unassigned_qty" - ${allocatedQty}`,
2187
+ unassignedUomValue: () => `"unassigned_uom_value" - ${allocatedUomValue}`
2188
+ })
2189
+ .where({ productDetail: orderProducts[i].productDetail.id })
2190
+ .execute()
2191
+
2192
+ // update order product status to ASSIGNED
2193
+ await this.trxMgr
2194
+ .getRepository(OrderProduct)
2195
+ .update({ id: orderProducts[i].id }, { status: ORDER_PRODUCT_STATUS.ASSIGNED, updater: this.user })
2196
+
2197
+ // Create new order inventory with status -> READY_TO_PICK, type -> RELEASE_OF_GOODS
2198
+ let newTargetInventory: OrderInventory = new OrderInventory()
2199
+ newTargetInventory = {
2200
+ domain: this.domain,
2201
+ bizplace: orderProducts[i].bizplace,
2202
+ name: OrderNoGenerator.orderInventory(),
2203
+ releaseGood: orderProducts[i].releaseGood,
2204
+ batchId: orderProducts[i].batchId,
2205
+ status: ORDER_INVENTORY_STATUS.PICKING,
2206
+ type: ORDER_TYPES.RELEASE_OF_GOODS,
2207
+ refWorksheetId: worksheet.id,
2208
+ product: orderProducts[i].product,
2209
+ productDetail: orderProducts[i].productDetail,
2210
+ packingType: orderProducts[i].packingType,
2211
+ packingSize: parseFloat(orderProducts[i].packingSize),
2212
+ creator: this.user,
2213
+ inventory: targetInventory,
2214
+ releaseQty: allocatedQty,
2215
+ releaseUomValue: allocatedUomValue,
2216
+ uom: orderProducts[i].uom,
2217
+ orderProduct: orderProducts[i]
2218
+ }
2219
+ newTargetInventory = await this.trxMgr.getRepository(OrderInventory).save(newTargetInventory)
2220
+
2221
+ if (idx == 0) {
2222
+ let updatedWorksheetDetail = {
2223
+ ...worksheet.worksheetDetails[i],
2224
+ targetInventory: newTargetInventory,
2225
+ worksheet,
2226
+ bizplace: worksheet.bizplace,
2227
+ domain: this.domain,
2228
+ updater: this.user,
2229
+ creator: this.user
2230
+ }
2231
+ await this.trxMgr.getRepository(WorksheetDetail).update(worksheet.worksheetDetails[i].id, {
2232
+ targetInventory: newTargetInventory,
2233
+ worksheet,
2234
+ bizplace: worksheet.bizplace,
2235
+ domain: this.domain,
2236
+ updater: this.user,
2237
+ creator: this.user
2238
+ })
2239
+
2240
+ newWorksheetDetails.push(updatedWorksheetDetail)
2241
+ } else {
2242
+ let newWorksheetDetail = {
2243
+ ...worksheet.worksheetDetails[i],
2244
+ name: WorksheetNoGenerator.batchPickingDetail(),
2245
+ targetInventory: newTargetInventory,
2246
+ worksheet,
2247
+ bizplace: worksheet.bizplace,
2248
+ domain: this.domain,
2249
+ updater: this.user,
2250
+ creator: this.user
2251
+ }
2252
+ delete newWorksheetDetail.id
2253
+
2254
+ newWorksheetDetail = await this.trxMgr.getRepository(WorksheetDetail).save(newWorksheetDetail)
2255
+ newWorksheetDetails.push(newWorksheetDetail)
2256
+ }
2257
+ } else {
2258
+ idx = idx + 1
2259
+ }
2260
+ }
2261
+ await this.trxMgr.getRepository(ReleaseGood).update(releaseGoodIds, { assignedInventory: true })
2262
+ }
2263
+ return newWorksheetDetails
2264
+ }
1973
2265
  }
@@ -12,8 +12,8 @@ import { ViewColumn, ViewEntity } from 'typeorm'
12
12
  i.uom,
13
13
  i.product_id as "product_id",
14
14
  null as "product_bundle_id",
15
- COALESCE(SUM(COALESCE(i.qty, 0)) - SUM(COALESCE(i.locked_qty, 0))) AS "remain_qty",
16
- COALESCE(SUM(COALESCE(i.uom_value, 0)) - SUM(COALESCE(i.locked_uom_value, 0))) AS "remain_uom_value",
15
+ COALESCE(SUM(COALESCE(i.qty, 0)) - SUM(COALESCE(i.locked_qty, 0)) - SUM(COALESCE(pds.unassigned_qty, 0))) AS "remain_qty",
16
+ COALESCE(SUM(COALESCE(i.uom_value, 0)) - SUM(COALESCE(i.locked_uom_value, 0)) - SUM(COALESCE(pds.unassigned_uom_value, 0))) AS "remain_uom_value",
17
17
  COALESCE(sum(COALESCE(i.qty, 0::double precision))) AS "qty",
18
18
  COALESCE(sum(COALESCE(i.uom_value, 0::double precision))) AS "uom_value",
19
19
  COALESCE(sum(COALESCE(i.transfer_qty, 0::double precision))) AS "transfer_qty",
@@ -22,6 +22,7 @@ import { ViewColumn, ViewEntity } from 'typeorm'
22
22
  FROM inventories i
23
23
  INNER JOIN locations l2 ON i.location_id = l2.id AND i.domain_id = l2.domain_id AND l2.type NOT IN ('QUARANTINE', 'RESERVE')
24
24
  INNER JOIN products p ON p.id = i.product_id
25
+ LEFT JOIN product_detail_stocks pds on pds.product_detail_id = i.product_detail_id
25
26
  WHERE i.status = 'STORED' AND CASE WHEN i.expiration_date is not null and p.shelf_life is not null then CURRENT_DATE < i.expiration_date - p.shelf_life else true end
26
27
  AND i.obsolete is false
27
28
  GROUP by
@@ -51,14 +52,15 @@ import { ViewColumn, ViewEntity } from 'typeorm'
51
52
  FROM product_bundles pb
52
53
  INNER JOIN (
53
54
  SELECT i.domain_id, i.bizplace_id, pbs.product_id, pbs.product_bundle_id, min(pbs.bundle_qty),
54
- (SUM(COALESCE(i.qty, 0)) - SUM(COALESCE(i.locked_qty, 0))) / min(pbs.bundle_qty) AS "available_qty",
55
- (SUM(COALESCE(i.uom_value, 0)) - SUM(COALESCE(i.locked_uom_value, 0))) / min(pbs.bundle_qty) AS "available_uom_value",
55
+ (SUM(COALESCE(i.qty, 0)) - SUM(COALESCE(i.locked_qty, 0)) - SUM(COALESCE(pds.unassigned_qty, 0))) / min(pbs.bundle_qty) AS "available_qty",
56
+ (SUM(COALESCE(i.uom_value, 0)) - SUM(COALESCE(i.locked_uom_value, 0)) - SUM(COALESCE(pds.unassigned_uom_value, 0))) / min(pbs.bundle_qty) AS "available_uom_value",
56
57
  (sum(COALESCE(i.qty, 0::double precision))) / min(pbs.bundle_qty)::double precision AS "qty",
57
58
  (sum(COALESCE(i.uom_value, 0::double precision))) / min(pbs.bundle_qty)::double precision AS "uom_value",
58
59
  (sum(COALESCE(i.transfer_qty, 0::double precision))) / min(pbs.bundle_qty)::double precision AS "transfer_qty",
59
60
  (sum(COALESCE(i.transfer_uom_value, 0::double precision))) / min(pbs.bundle_qty)::double precision AS "transfer_uom_value"
60
61
  FROM product_bundle_settings pbs
61
62
  LEFT JOIN inventories i ON i.product_id = pbs.product_id AND i.status = 'STORED' AND i.obsolete = false
63
+ LEFT JOIN product_detail_stocks pds ON pds.product_detail_id = i.product_detail_id
62
64
  INNER JOIN products p ON p.id = pbs.product_id AND CASE WHEN i.expiration_date is not null and p.shelf_life is not null then CURRENT_DATE < i.expiration_date - p.shelf_life else true end
63
65
  INNER JOIN locations l ON i.location_id = l.id AND i.domain_id = l.domain_id AND l.type NOT IN ('QUARANTINE', 'RESERVE')
64
66
  GROUP by
@@ -1,28 +1,17 @@
1
- import {
2
- Brackets,
3
- getRepository,
4
- SelectQueryBuilder
5
- } from 'typeorm'
1
+ import { Brackets, getRepository, SelectQueryBuilder } from 'typeorm'
6
2
 
7
3
  import { User } from '@things-factory/auth-base'
8
4
  import { getPermittedBizplaceIds } from '@things-factory/biz-base'
9
5
  import { ORDER_INVENTORY_STATUS } from '@things-factory/sales-base'
10
- import {
11
- buildCondition,
12
- buildQuery,
13
- Domain
14
- } from '@things-factory/shell'
15
- import {
16
- Inventory,
17
- LOCATION_TYPE
18
- } from '@things-factory/warehouse-base'
6
+ import { buildCondition, buildQuery, Domain } from '@things-factory/shell'
7
+ import { Inventory, LOCATION_TYPE } from '@things-factory/warehouse-base'
19
8
 
20
9
  export const inventoriesByPalletResolver = {
21
10
  async inventoriesByPallet(_: any, { filters, pagination, sortings, locationSortingRules }, context: any) {
22
11
  const { domain, user }: { domain: Domain; user: User } = context.state
23
12
  const params = { filters, pagination }
24
13
  let permittedBizplaceIds: string[] = await getPermittedBizplaceIds(domain, user)
25
- const bizplaceId = params.filters.find(x => x.name == 'bizplace_id')
14
+ const bizplaceId = params.filters.find(x => x.name == 'bizplace_id')
26
15
  const productFilters = params.filters.filter(x => x.name == 'productName')
27
16
  const recallFilters = params.filters.find(x => x.name === 'recall')
28
17
  const skipLockCheckFilters = params.filters.find(x => x.name === 'skipLockCheck')
@@ -46,6 +35,7 @@ export const inventoriesByPalletResolver = {
46
35
  .leftJoinAndSelect('iv.bizplace', 'bizplace')
47
36
  .leftJoinAndSelect('iv.product', 'product')
48
37
  .leftJoinAndSelect('iv.productDetail', 'productDetail')
38
+ .leftJoin('product_detail_stocks', 'pds', 'pds.product_detail_id = iv.product_detail_id')
49
39
  .leftJoinAndSelect('iv.warehouse', 'warehouse')
50
40
  .leftJoinAndSelect('iv.location', 'location')
51
41
  .leftJoinAndSelect('iv.creator', 'creator')
@@ -54,7 +44,9 @@ export const inventoriesByPalletResolver = {
54
44
  .andWhere('iv.transfer_qty <= 0')
55
45
  .andWhere('iv.transfer_uom_value <= 0')
56
46
  .andWhere(
57
- `location.type ${recallFilters?.value === true ? '' : 'NOT'} IN ('${LOCATION_TYPE.QUARANTINE}', '${LOCATION_TYPE.RESERVE}', '${LOCATION_TYPE.DAMAGE}')`
47
+ `location.type ${recallFilters?.value === true ? '' : 'NOT'} IN ('${LOCATION_TYPE.QUARANTINE}', '${
48
+ LOCATION_TYPE.RESERVE
49
+ }', '${LOCATION_TYPE.DAMAGE}')`
58
50
  )
59
51
  .andWhere(
60
52
  `(iv.batch_id, product.name, iv.packing_type, product.brand) NOT IN (
@@ -71,14 +63,43 @@ export const inventoriesByPalletResolver = {
71
63
  AND oi.domain_id = (:domainId)
72
64
  AND oi.bizplace_id = (:bizplaceId)
73
65
  )`,
74
- { bizplaceId: bizplaceId.value , domainId: domain.id }
66
+ { bizplaceId: bizplaceId.value, domainId: domain.id }
75
67
  )
76
68
 
77
- if (locationFilters) {
78
- qb.andWhere
79
- (`location.name ilike '${locationFilters.value}'`)
80
- }
69
+ if (locationFilters) {
70
+ qb.andWhere(`location.name ilike '${locationFilters.value}'`)
71
+ }
72
+
73
+ if (productFilters && productFilters.length > 0) {
74
+ let productInfo = productFilters[0]
75
+ qb.andWhere(
76
+ new Brackets(qb2 => {
77
+ productFilterColumns.forEach(filter => {
78
+ const condition = buildCondition(
79
+ 'product',
80
+ filter,
81
+ 'i_like',
82
+ productInfo.value,
83
+ false,
84
+ Object.keys(qb.getParameters()).length
85
+ )
86
+
87
+ qb2.orWhere(condition.clause, condition.parameters)
88
+ })
89
+ })
90
+ )
91
+ }
81
92
 
93
+ if (!skipLockCheck) {
94
+ qb.andWhere('CASE WHEN iv.lockedQty IS NULL THEN 0 ELSE iv.lockedQty END >= 0')
95
+ qb.andWhere('iv.qty - CASE WHEN iv.lockedQty IS NULL THEN 0 ELSE iv.lockedQty END > 0')
96
+ }
97
+
98
+ if (recallFilters?.value === true) {
99
+ qb.orWhere('(iv.domain_id = (:domainId) and iv.bizplace_id = (:bizplaceId)')
100
+ if (locationFilters) {
101
+ qb.andWhere(`location.name ilike '${locationFilters.value}'`)
102
+ }
82
103
  if (productFilters && productFilters.length > 0) {
83
104
  let productInfo = productFilters[0]
84
105
  qb.andWhere(
@@ -92,53 +113,18 @@ export const inventoriesByPalletResolver = {
92
113
  false,
93
114
  Object.keys(qb.getParameters()).length
94
115
  )
95
-
116
+
96
117
  qb2.orWhere(condition.clause, condition.parameters)
97
118
  })
98
119
  })
99
120
  )
100
121
  }
101
-
102
- if (!skipLockCheck) {
103
- qb.andWhere('CASE WHEN iv.lockedQty IS NULL THEN 0 ELSE iv.lockedQty END >= 0')
104
- qb.andWhere('iv.qty - CASE WHEN iv.lockedQty IS NULL THEN 0 ELSE iv.lockedQty END > 0')
105
- }
106
-
107
- if (recallFilters?.value === true) {
108
- qb.orWhere(
109
- '(iv.domain_id = (:domainId) and iv.bizplace_id = (:bizplaceId)'
110
- )
111
- if (locationFilters){
112
- qb.andWhere
113
- (`location.name ilike '${locationFilters.value}'`)
114
- }
115
- if (productFilters && productFilters.length > 0) {
116
- let productInfo = productFilters[0]
117
- qb.andWhere(
118
- new Brackets(qb2 => {
119
- productFilterColumns.forEach(filter => {
120
- const condition = buildCondition(
121
- 'product',
122
- filter,
123
- 'i_like',
124
- productInfo.value,
125
- false,
126
- Object.keys(qb.getParameters()).length
127
- )
128
-
129
- qb2.orWhere(condition.clause, condition.parameters)
130
- })
131
- })
132
- )
133
- }
134
- if (batchIdFilters) {
135
- qb.andWhere(
136
- `iv.batch_id ilike '${batchIdFilters.value}'`
137
- )
138
- }
122
+ if (batchIdFilters) {
123
+ qb.andWhere(`iv.batch_id ilike '${batchIdFilters.value}'`)
124
+ }
139
125
  qb.andWhere(
140
126
  'iv.obsolete = true and case when iv.expiration_date is not null and product.shelf_life is not null then CURRENT_DATE > iv.expiration_date - product.shelf_life else true end)'
141
- )
127
+ )
142
128
  } else {
143
129
  qb.andWhere('iv.obsolete = false')
144
130
  qb.andWhere(
@@ -199,4 +185,4 @@ async function getRemainAmount(inventory: Inventory): Promise<{ remainQty: numbe
199
185
  remainQty: inventory.qty - (inventory.lockedQty || 0),
200
186
  remainUomValue: inventory.uomValue - (inventory.lockedUomValue || 0)
201
187
  }
202
- }
188
+ }