@things-factory/warehouse-base 8.0.2 → 8.0.5

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 (88) hide show
  1. package/dist-server/tsconfig.tsbuildinfo +1 -1
  2. package/package.json +8 -8
  3. package/server/constants/index.ts +0 -5
  4. package/server/constants/inventory.ts +0 -67
  5. package/server/constants/location.ts +0 -14
  6. package/server/constants/pallet.ts +0 -10
  7. package/server/constants/rule-type.ts +0 -5
  8. package/server/constants/tote.ts +0 -5
  9. package/server/controllers/ecommerce/ecommerce-controller.ts +0 -108
  10. package/server/controllers/ecommerce/index.ts +0 -2
  11. package/server/controllers/ecommerce/sellercraft-controller.ts +0 -100
  12. package/server/controllers/index.ts +0 -2
  13. package/server/controllers/warehouse-controller.ts +0 -181
  14. package/server/index.ts +0 -9
  15. package/server/middlewares/index.ts +0 -0
  16. package/server/migrations/index.ts +0 -9
  17. package/server/service/index.ts +0 -80
  18. package/server/service/inventory/index.ts +0 -6
  19. package/server/service/inventory/inventory-mutation.ts +0 -530
  20. package/server/service/inventory/inventory-query.ts +0 -1263
  21. package/server/service/inventory/inventory-types.ts +0 -367
  22. package/server/service/inventory/inventory.ts +0 -408
  23. package/server/service/inventory-change/index.ts +0 -6
  24. package/server/service/inventory-change/inventory-change-mutation.ts +0 -969
  25. package/server/service/inventory-change/inventory-change-query.ts +0 -93
  26. package/server/service/inventory-change/inventory-change-types.ts +0 -36
  27. package/server/service/inventory-change/inventory-change.ts +0 -164
  28. package/server/service/inventory-history/index.ts +0 -6
  29. package/server/service/inventory-history/inventory-history-mutation.ts +0 -116
  30. package/server/service/inventory-history/inventory-history-query.ts +0 -1845
  31. package/server/service/inventory-history/inventory-history-types.ts +0 -444
  32. package/server/service/inventory-history/inventory-history.ts +0 -203
  33. package/server/service/inventory-item/index.ts +0 -6
  34. package/server/service/inventory-item/inventory-item-mutation.ts +0 -217
  35. package/server/service/inventory-item/inventory-item-query.ts +0 -226
  36. package/server/service/inventory-item/inventory-item-type.ts +0 -74
  37. package/server/service/inventory-item/inventory-item.ts +0 -105
  38. package/server/service/inventory-item-change/index.ts +0 -6
  39. package/server/service/inventory-item-change/inventory-item-change-mutation.ts +0 -119
  40. package/server/service/inventory-item-change/inventory-item-change-query.ts +0 -47
  41. package/server/service/inventory-item-change/inventory-item-change-type.ts +0 -68
  42. package/server/service/inventory-item-change/inventory-item-change.ts +0 -92
  43. package/server/service/inventory-product/index.ts +0 -6
  44. package/server/service/inventory-product/inventory-product-mutation.ts +0 -116
  45. package/server/service/inventory-product/inventory-product-query.ts +0 -47
  46. package/server/service/inventory-product/inventory-product-type.ts +0 -59
  47. package/server/service/inventory-product/inventory-product.ts +0 -88
  48. package/server/service/location/index.ts +0 -6
  49. package/server/service/location/location-mutation.ts +0 -134
  50. package/server/service/location/location-query.ts +0 -244
  51. package/server/service/location/location-types.ts +0 -173
  52. package/server/service/location/location.ts +0 -121
  53. package/server/service/movement/index.ts +0 -6
  54. package/server/service/movement/movement-mutation.ts +0 -60
  55. package/server/service/movement/movement-query.ts +0 -263
  56. package/server/service/movement/movement-types.ts +0 -74
  57. package/server/service/movement/movement.ts +0 -81
  58. package/server/service/pallet/index.ts +0 -6
  59. package/server/service/pallet/pallet-mutation.ts +0 -242
  60. package/server/service/pallet/pallet-query.ts +0 -106
  61. package/server/service/pallet/pallet-types.ts +0 -80
  62. package/server/service/pallet/pallet.ts +0 -92
  63. package/server/service/pallet-count/index.ts +0 -6
  64. package/server/service/pallet-count/pallet-count-mutation.ts +0 -151
  65. package/server/service/pallet-count/pallet-count-query.ts +0 -45
  66. package/server/service/pallet-count/pallet-count-types.ts +0 -36
  67. package/server/service/pallet-count/pallet-count.ts +0 -70
  68. package/server/service/pallet-history/index.ts +0 -6
  69. package/server/service/pallet-history/pallet-history-mutation.ts +0 -114
  70. package/server/service/pallet-history/pallet-history-query.ts +0 -48
  71. package/server/service/pallet-history/pallet-history-types.ts +0 -36
  72. package/server/service/pallet-history/pallet-history.ts +0 -89
  73. package/server/service/reduced-inventory-history/index.ts +0 -3
  74. package/server/service/reduced-inventory-history/reduced-inventory-history.ts +0 -92
  75. package/server/service/tote/index.ts +0 -6
  76. package/server/service/tote/tote-mutation.ts +0 -201
  77. package/server/service/tote/tote-query.ts +0 -106
  78. package/server/service/tote/tote-types.ts +0 -44
  79. package/server/service/tote/tote.ts +0 -77
  80. package/server/service/warehouse/index.ts +0 -6
  81. package/server/service/warehouse/warehouse-mutation.ts +0 -152
  82. package/server/service/warehouse/warehouse-query.ts +0 -58
  83. package/server/service/warehouse/warehouse-types.ts +0 -50
  84. package/server/service/warehouse/warehouse.ts +0 -95
  85. package/server/utils/datetime-util.ts +0 -54
  86. package/server/utils/index.ts +0 -3
  87. package/server/utils/inventory-no-generator.ts +0 -15
  88. package/server/utils/inventory-util.ts +0 -490
@@ -1,1263 +0,0 @@
1
- import { Arg, Args, Ctx, Directive, FieldResolver, Query, Resolver, Root } from 'type-graphql'
2
- import { Brackets, EntityManager, Equal, Not, OrderByCondition, Repository, SelectQueryBuilder } from 'typeorm'
3
-
4
- import { User } from '@things-factory/auth-base'
5
- import { Bizplace, getPartnersCompanyBizplaces, getPermittedBizplaceIds } from '@things-factory/biz-base'
6
- import { logger } from '@things-factory/env'
7
- import { Product, ProductBundleSetting } from '@things-factory/product-base'
8
- import { Setting } from '@things-factory/setting-base'
9
- import {
10
- buildQuery,
11
- Domain,
12
- Filter,
13
- getQueryBuilderFromListParams,
14
- getRepository,
15
- ListParam,
16
- Pagination,
17
- Sorting
18
- } from '@things-factory/shell'
19
-
20
- import { INVENTORY_STATUS, LOCATION_TYPE } from '../../constants'
21
- import { InventoryChange } from '../inventory-change/inventory-change'
22
- import { Inventory } from './inventory'
23
- import { InventoryBundleGroupDetail, InventoryList } from './inventory-types'
24
-
25
- @Resolver(Inventory)
26
- export class InventoryQuery {
27
- /**
28
- * Combined single query resolver to perform extraction of data with or without pagination
29
- * @param context
30
- * @param filters
31
- * @param pagination
32
- * @param sortings
33
- * @param locationSortingRules
34
- * @param exportItem
35
- * @returns
36
- */
37
- @Directive('@privilege(category: "inventory", privilege: "query", domainOwnerGranted: true)')
38
- @Directive('@transaction')
39
- @Query(returns => InventoryList)
40
- async inventories(
41
- @Ctx() context: ResolverContext,
42
- @Arg('filters', type => [Filter], { nullable: true }) filters?: Filter[],
43
- @Arg('pagination', type => Pagination, { nullable: true }) pagination?: Pagination,
44
- @Arg('sortings', type => [Sorting], { nullable: true }) sortings?: Sorting[],
45
- @Arg('locationSortingRules', type => [Sorting], { nullable: true }) locationSortingRules?: Sorting[],
46
- @Arg('exportItem', type => Boolean, { nullable: true }) exportItem?: Boolean
47
- ): Promise<InventoryList> {
48
- const { domain, user, tx } = context.state
49
- const { page, limit } = pagination || {}
50
-
51
- try {
52
- //Define special filters
53
- const productFilters = filters.find((filter: any) => filter.name == 'productInfo')
54
- const remainOnlyParam = filters.find((filter: any) => filter.name == 'remainOnly')
55
- const bizplace = filters.find((filter: any) => filter.name === 'bizplace')
56
-
57
- filters = filters.filter(x => ['productInfo', 'remainOnly'].indexOf(x.name) < 0)
58
-
59
- const params = { filters, pagination }
60
-
61
- const remainOnly: boolean = remainOnlyParam?.value || false
62
-
63
- const qb: SelectQueryBuilder<Inventory> = getQueryBuilderFromListParams({
64
- repository: tx.getRepository(Inventory),
65
- params,
66
- domain,
67
- alias: 'inventory',
68
- searchables: ['warehouse', 'product', 'batchId', 'location', 'palletId', 'lot']
69
- })
70
-
71
- qb.leftJoinAndSelect('inventory.bizplace', 'bizplace')
72
- .leftJoinAndSelect('inventory.product', 'product')
73
- .leftJoinAndSelect('product.productDetails', 'productDetail', 'productDetail.id = inventory.product_detail_id')
74
- .leftJoinAndSelect('inventory.warehouse', 'warehouse')
75
- .leftJoinAndSelect('inventory.location', 'location')
76
- .leftJoinAndSelect('inventory.creator', 'creator')
77
- .leftJoinAndSelect('inventory.updater', 'updater')
78
-
79
- if (!bizplace) {
80
- const bizplaces = await getPermittedBizplaceIds(domain, user)
81
-
82
- qb.andWhere(
83
- new Brackets(qb => {
84
- qb.orWhere('inventory.bizplace_id IN (:...bizplaces)', { bizplaces })
85
- })
86
- )
87
- }
88
-
89
- // To get aggregated serial number in csv and total number of stored serial number
90
- qb.leftJoinAndSelect(
91
- subQuery => {
92
- return subQuery
93
- .select('inventoryItems.inventory_id', 'inventory_item_inventory_id')
94
- .addSelect(`SUM(case when "inventoryItems"."status" = 'STORED' then 1 else 0 end)`, 'inventory_item_count')
95
- .addSelect(`string_agg(inventoryItems.serial_number, ', ')`, 'serial_numbers')
96
- .from('inventory_items', 'inventoryItems')
97
- .where(`inventoryItems.domain_id = :domainId`, { domainId: domain.id })
98
- .andWhere(`inventoryItems.status = :ivicStatus`, { ivicStatus: INVENTORY_STATUS.STORED })
99
- .groupBy('inventoryItems.inventory_id')
100
- },
101
- 'inventoryItems',
102
- '"inventoryItems"."inventory_item_inventory_id" = "inventory"."id"'
103
- )
104
-
105
- // To get inventory with remaining qty
106
- if (remainOnly) {
107
- qb.andWhere('inventory.qty > 0')
108
- .andWhere('CASE WHEN inventory.lockedQty IS NULL THEN 0 ELSE inventory.lockedQty END >= 0')
109
- .andWhere('inventory.qty - CASE WHEN inventory.lockedQty IS NULL THEN 0 ELSE inventory.lockedQty END > 0')
110
- }
111
-
112
- // Filter based on multiple product parameters and allow to search in csv format
113
- if (productFilters) {
114
- let productFilterValue = `%${productFilters.value.toLowerCase()}%`
115
- qb.andWhere(qb => {
116
- const subQuery = qb
117
- .subQuery()
118
- .select()
119
- .from(Product, `products`)
120
- .where(`products.id = Inventory.product_id`) // @chrislim Does the uppercase I in Inventory affect? I can see the rest are in lowercase i
121
- .andWhere(
122
- new Brackets(qb => {
123
- qb.where('Lower(products.sku) LIKE :productInfo', { productInfo: productFilterValue })
124
- .orWhere('Lower(products.name) LIKE :productInfo', { productInfo: productFilterValue })
125
- .orWhere('Lower(products.description) LIKE :productInfo', { productInfo: productFilterValue })
126
- .orWhere('Lower(products.brand) LIKE :productInfo', { productInfo: productFilterValue })
127
- })
128
- )
129
- .getQuery()
130
- return `EXISTS ${subQuery}`
131
- })
132
- }
133
-
134
- // Apply sorting based on child data
135
- if (sortings?.length !== 0) {
136
- const arrChildSortData = ['bizplace', 'product', 'location', 'warehouse', 'zone']
137
- const sort: OrderByCondition = (sortings || []).reduce(
138
- (acc, sort) => ({
139
- ...acc,
140
- [arrChildSortData.indexOf(sort.name) >= 0 ? sort.name + '.name' : 'inventory.' + sort.name]: sort.desc
141
- ? 'DESC'
142
- : 'ASC'
143
- }),
144
- {}
145
- )
146
- qb.orderBy(sort)
147
- }
148
-
149
- if (locationSortingRules?.length > 0) {
150
- locationSortingRules.forEach(rule => {
151
- qb.addOrderBy(`location.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
152
- })
153
- }
154
-
155
- // Fetch all row for exporting
156
- if (exportItem != true && page && limit) {
157
- qb.offset((page - 1) * limit).limit(limit)
158
- }
159
-
160
- let items = (await qb.getRawMany()).map(item => {
161
- return {
162
- ...new Inventory(item),
163
- serialNumbers: item.serial_numbers
164
- }
165
- })
166
- let total = await qb.getCount()
167
-
168
- return {
169
- items,
170
- total
171
- }
172
- } catch (error) {
173
- logger.error(`inventory-query[inventories]`, error)
174
- throw error
175
- }
176
- }
177
-
178
- @Directive('@privilege(category: "inventory", privilege: "query", domainOwnerGranted: true)')
179
- @Query(returns => Inventory)
180
- async inventory(@Arg('palletId') palletId: string, @Ctx() context: ResolverContext): Promise<Inventory> {
181
- const { domain } = context.state
182
-
183
- return await getRepository(Inventory).findOne({
184
- where: { domain: { id: domain.id }, palletId },
185
- relations: ['domain', 'bizplace', 'product', 'location', 'warehouse', 'creator', 'updater']
186
- })
187
- }
188
-
189
- @Directive('@privilege(category: "inventory", privilege: "query", domainOwnerGranted: true)')
190
- @Query(returns => Inventory)
191
- async inventoryById(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Inventory> {
192
- const { domain } = context.state
193
-
194
- return await getRepository(Inventory).findOne({
195
- where: { domain: { id: domain.id }, id },
196
- relations: ['domain', 'bizplace', 'product', 'location', 'warehouse', 'creator', 'updater']
197
- })
198
- }
199
-
200
- @Directive('@privilege(category: "inventory", privilege: "query", domainOwnerGranted: true)')
201
- @Query(returns => Inventory)
202
- async inventoryByPallet(@Arg('palletId') palletId: string, @Ctx() context: ResolverContext): Promise<Inventory> {
203
- const { domain } = context.state
204
-
205
- return await getRepository(Inventory).findOne({
206
- where: { domain: { id: domain.id }, palletId, status: Not(Equal(INVENTORY_STATUS.TERMINATED)) },
207
- relations: ['domain', 'bizplace', 'product', 'location', 'warehouse', 'creator', 'updater']
208
- })
209
- }
210
-
211
- @Directive('@privilege(category: "inventory", privilege: "query", domainOwnerGranted: true)')
212
- @Query(returns => InventoryList)
213
- async renewInventoriesGroupByProduct(
214
- @Args(type => ListParam) params: ListParam,
215
- @Ctx() context: ResolverContext
216
- ): Promise<InventoryList> {
217
- const { domain } = context.state
218
-
219
- const queryBuilder = getQueryBuilderFromListParams({
220
- repository: getRepository(Inventory),
221
- params,
222
- alias: 'i',
223
- domain,
224
- searchables: ['productName', 'productSKU', 'productType'],
225
- filtersMap: {
226
- productName: {
227
- relationColumn: 'product',
228
- columnName: 'name'
229
- },
230
- productSKU: {
231
- relationColumn: 'product',
232
- columnName: 'sku'
233
- },
234
- productType: {
235
- relationColumn: 'product',
236
- columnName: 'type'
237
- }
238
- }
239
- })
240
-
241
- queryBuilder
242
- .select('product.id', 'productId')
243
- .addSelect('product.name', 'productName')
244
- .addSelect('product.sku', 'productSKU')
245
- .addSelect('product.type', 'productType')
246
- .addSelect('product.primaryValue', 'primaryValue')
247
- .addSelect('product.primaryUnit', 'primaryUnit')
248
- .addSelect('product.auxUnit1', 'auxUnit1')
249
- .addSelect('product.minQty', 'minQty')
250
- .addSelect('product.maxQty', 'maxQty')
251
- .addSelect('i.product_color', 'productColor')
252
- .addSelect('i.product_quality', 'productQuality')
253
- .addSelect('i.work_category', 'workCategory')
254
- .addSelect('i.packing_type', 'packingType')
255
- .addSelect('COALESCE(SUM(i.qty), 0)', 'qty')
256
- .addSelect('COALESCE(SUM(i.locked_qty), 0)', 'lockedQty')
257
- .addSelect('COALESCE(SUM(i.uom_value), 0)', 'uomValue')
258
- .addSelect('COALESCE(SUM(i.locked_uom_value), 0)', 'lockedUomValue')
259
- .innerJoin('i.product', 'product', 'product.deleted_at IS NULL')
260
- .andWhere('i.status = :status', { status: INVENTORY_STATUS.STORED })
261
- .andWhere('i.expiration_date >= now()')
262
- .andWhere('product.domain_id = :domain', { domain: domain.id })
263
- .groupBy('product.id')
264
- .addGroupBy('i.product_color')
265
- .addGroupBy('i.product_quality')
266
- .addGroupBy('i.packing_type')
267
- .addGroupBy('i.work_category')
268
-
269
- queryBuilder.orderBy('product.sku', 'ASC')
270
-
271
- const items = await queryBuilder.getRawMany()
272
- return { items, total: items.length }
273
- }
274
-
275
- @Directive('@privilege(category: "inventory", privilege: "query", domainOwnerGranted: true)')
276
- @Directive('@transaction')
277
- @Query(returns => InventoryList)
278
- async inventoriesByProduct(
279
- @Args(type => ListParam) params: ListParam,
280
- @Ctx() context: ResolverContext
281
- ): Promise<InventoryList> {
282
- try {
283
- const { domain, user, tx } = context.state
284
- let permittedBizplaceIds: string[] = await getPermittedBizplaceIds(domain, user)
285
- const partnersCompanyBizplaces: Bizplace[] = await getPartnersCompanyBizplaces(domain, user)
286
-
287
- const page = params.pagination.page
288
- const limit = params.pagination.limit
289
-
290
- let bizplaceFilter = params.filters.find(filter => filter.name == 'bizplace')
291
- let productFilter = params.filters.find(filter => filter.name == 'product')
292
- let packingTypeFilter = params.filters.find(filter => filter.name == 'packingType')
293
- let availableStockFilter = params.filters.find(filter => filter.name == 'availableStock')
294
- let lowStockFilter = params.filters.find(filter => filter.name == 'lowStock')
295
- let overStockFilter = params.filters.find(filter => filter.name == 'overStock')
296
- let quarantineStockFilter = params.filters.find(filter => filter.name == 'quarantineStock')
297
- let reserveStockFilter = params.filters.find(filter => filter.name == 'reserveStock')
298
- let productTypeFilter = params.filters.find(filter => filter.name == 'type')
299
-
300
- if (bizplaceFilter) {
301
- const bizplaceQueryBuilder: SelectQueryBuilder<Bizplace> = getRepository(Bizplace).createQueryBuilder('b')
302
- bizplaceQueryBuilder
303
- .innerJoin('companies', 'c2', ' c2.domain_id = b.domain_id')
304
- .innerJoin('bizplaces', 'b2', 'b2.company_id = c2.id')
305
- .where('b2.id = :bizplaceId', { bizplaceId: bizplaceFilter.value })
306
-
307
- let bizplaceList = await bizplaceQueryBuilder.getMany()
308
-
309
- permittedBizplaceIds = [
310
- ...permittedBizplaceIds.filter(itm => itm == bizplaceFilter.value),
311
- ...bizplaceList.map(bz => bz.id),
312
- ...partnersCompanyBizplaces.map(biz => biz.id)
313
- ]
314
- }
315
-
316
- let bizplaceQuery = ''
317
- if (permittedBizplaceIds.length > 0) {
318
- bizplaceQuery = `AND EXISTS (
319
- SELECT * FROM (VALUES ${permittedBizplaceIds.map(id => `('${id}')`).join(',')})
320
- AS bizFilter(bizplace_id)
321
- WHERE bizFilter.bizplace_id::uuid = "Inventory"."bizplace_id"
322
- )`
323
- } else {
324
- bizplaceQuery = `1 = 0`
325
- }
326
-
327
- let productQuery = ''
328
- if (productFilter) {
329
- let productValue = productFilter.value
330
- .toLowerCase()
331
- .split(',')
332
- .map(prod => {
333
- return "'%" + prod.trim().replace(/'/g, "''") + "%'"
334
- })
335
- .join(',')
336
- productQuery = `AND (
337
- Lower("Product"."name") LIKE ANY(ARRAY[${productValue}])
338
- OR Lower("Product"."sku") LIKE ANY(ARRAY[${productValue}])
339
- OR Lower("Product"."brand") LIKE ANY(ARRAY[${productValue}])
340
- OR Lower("Product"."description") LIKE ANY(ARRAY[${productValue}])
341
- OR Lower("ProductRef"."name") LIKE ANY(ARRAY[${productValue}])
342
- OR Lower("ProductRef"."sku") LIKE ANY(ARRAY[${productValue}])
343
- OR Lower("ProductRef"."description") LIKE ANY(ARRAY[${productValue}])
344
- )`
345
- }
346
-
347
- if (productTypeFilter) {
348
- let productTypeValue = productTypeFilter.value
349
- .toLowerCase()
350
- .split(',')
351
- .map(prod => {
352
- return "'%" + prod.trim().replace(/'/g, "''") + "%'"
353
- })
354
- .join(',')
355
- productQuery =
356
- productQuery +
357
- ` AND (
358
- Lower("Product"."type") LIKE ANY(ARRAY[${productTypeValue}])
359
- )`
360
- }
361
-
362
- let qtyStockQuery = ''
363
-
364
- let availableStockQuery = ''
365
-
366
- if (reserveStockFilter?.value) {
367
- qtyStockQuery += `\nAND SUM("reserveQty") > 0`
368
- }
369
-
370
- if (quarantineStockFilter?.value) {
371
- qtyStockQuery += `\nAND SUM("quarantineQty") > 0`
372
- }
373
-
374
- if (availableStockFilter?.value) {
375
- qtyStockQuery += ` AND SUM("availableQty") > 0`
376
- }
377
-
378
- if (availableStockFilter?.value) {
379
- availableStockQuery = ` AND SUM("Inventory"."qty") > 0`
380
- }
381
-
382
- if (availableStockFilter?.value && quarantineStockFilter?.value) {
383
- qtyStockQuery = ''
384
- qtyStockQuery += `\nAND (SUM("availableQty") > 0 \n OR SUM("quarantineQty") > 0)`
385
- }
386
-
387
- if (availableStockFilter?.value && reserveStockFilter?.value) {
388
- qtyStockQuery = ''
389
- qtyStockQuery += `\nAND (SUM("availableQty") > 0 \n OR SUM("reserveQty") > 0)`
390
- }
391
-
392
- if (quarantineStockFilter?.value && reserveStockFilter?.value) {
393
- qtyStockQuery = ''
394
- qtyStockQuery += `\nAND (SUM("quarantineQty") > 0 \n OR SUM("reserveQty") > 0)`
395
- }
396
-
397
- let inventoryQuery = ''
398
- if (packingTypeFilter) {
399
- let packingTypeValue = packingTypeFilter.value
400
- .toLowerCase()
401
- .split(',')
402
- .map(prod => {
403
- return "'%" + prod.trim().replace(/'/g, "''") + "%'"
404
- })
405
- .join(',')
406
-
407
- inventoryQuery =
408
- inventoryQuery +
409
- ` AND (
410
- Lower("Inventory"."packing_type") LIKE ANY(ARRAY[${packingTypeValue}])
411
- )`
412
- }
413
-
414
- let thresholdQuery = ''
415
- if (lowStockFilter?.value && overStockFilter?.value) {
416
- throw new Error('invalid filter combination')
417
- } else if (lowStockFilter?.value && !overStockFilter?.value) {
418
- thresholdQuery = `AND SUM(COALESCE("minQty", 0)) > SUM("availableQty")`
419
- } else if (!lowStockFilter?.value && overStockFilter?.value) {
420
- thresholdQuery = `AND SUM(COALESCE("maxQty", 0)) > 0 AND SUM(COALESCE("maxQty", 0)) < SUM("availableQty")`
421
- }
422
-
423
- await tx.query(
424
- `
425
- CREATE TEMP TABLE order_inventories_by_products AS (
426
- SELECT "product_id", "packing_type", sum("release_qty") AS "total_release_qty" FROM order_inventories oi
427
- WHERE "type" = 'RELEASE_OF_GOODS'
428
- AND "domain_id" = $1
429
- AND "inventory_id" ISNULL
430
- AND "status" IN ('PENDING','PENDING_RECEIVE','PENDING_WORKSHEET','READY_TO_PICK'/*, 'PENDING_SPLIT' kiv sebab kalau pending split, ada yang dah assign tapi belum delete*/)
431
- GROUP BY "product_id", "packing_type"
432
- )`,
433
- [domain.id]
434
- )
435
-
436
- await tx.query(
437
- `
438
- create temp table temp_inv_history AS
439
- (
440
- SELECT "Product"."id" AS "id", "Product"."sku" AS "sku", "Product"."brand" AS "brand", "Product"."name" AS "name", "Product"."type" AS "type", "Product"."description" AS "description",
441
- "Inventory"."packing_type" AS "packingType",
442
- "Product"."weight" AS "weight", "ProductRef"."id" AS "productRefId",
443
- "ProductRef"."description" AS "productRefDesciption", "Bizplace"."id" AS "bizplaceId", "Bizplace"."name" AS "bizplaceName", SUM("Inventory"."qty") AS "qty" ,
444
- CASE WHEN SUM("Inventory"."qty") > 0 THEN SUM(COALESCE("Inventory"."unit_cost", 0) * "Inventory"."qty")/ SUM("Inventory"."qty") ELSE 0 END AS "averageUnitCost",
445
- COALESCE("ProductDetailBizplaceSetting"."min_qty", "Product"."min_qty",0) AS "minQty",
446
- COALESCE("ProductDetailBizplaceSetting"."max_qty", "Product"."max_qty",0) AS "maxQty",
447
- CASE WHEN "Location"."type" NOT IN ('${LOCATION_TYPE.QUARANTINE}','${LOCATION_TYPE.RESERVE}')
448
- THEN
449
- CASE WHEN SUM("Inventory"."qty") > 0
450
- THEN SUM("Inventory"."qty")-SUM(COALESCE("Inventory"."locked_qty", 0) + COALESCE("OrderInventoriesByProduct"."total_release_qty", 0))
451
- ELSE 0 END
452
- ELSE 0 END AS "availableQty",
453
- SUM(COALESCE("Inventory"."locked_qty", 0) + COALESCE("OrderInventoriesByProduct"."total_release_qty", 0)) AS "releaseQty",
454
- CASE WHEN "Location"."type" = '${LOCATION_TYPE.QUARANTINE}'
455
- THEN
456
- CASE WHEN SUM("Inventory"."qty") > 0
457
- THEN SUM("Inventory"."qty")-SUM(COALESCE("Inventory"."locked_qty", 0) + COALESCE("OrderInventoriesByProduct"."total_release_qty", 0))
458
- ELSE 0 END
459
- ELSE 0 END AS "quarantineQty",
460
- CASE WHEN "Location"."type" = '${LOCATION_TYPE.RESERVE}'
461
- THEN
462
- CASE WHEN SUM("Inventory"."qty") > 0
463
- THEN SUM("Inventory"."qty")-SUM(COALESCE("Inventory"."locked_qty", 0) + COALESCE("OrderInventoriesByProduct"."total_release_qty", 0))
464
- ELSE 0 END
465
- ELSE 0 END AS "reserveQty",
466
- CASE WHEN "Location"."type" = '${LOCATION_TYPE.BIN}' THEN SUM("Inventory"."qty") ELSE 0 END AS "binQty"
467
- FROM "inventories" "Inventory"
468
- LEFT JOIN "products" "Product" ON "Product"."id"="Inventory"."product_id"
469
- LEFT JOIN "product_details" "ProductDetails" ON "ProductDetails"."product_id" = "Product"."id"
470
- AND "ProductDetails"."is_default"
471
- LEFT JOIN "product_detail_bizplace_settings" "ProductDetailBizplaceSetting" ON "ProductDetailBizplaceSetting"."product_detail_id" = "ProductDetails"."id"
472
- AND "ProductDetailBizplaceSetting"."domain_id" = "Inventory"."domain_id"
473
- LEFT JOIN "products" "ProductRef" ON "ProductRef"."id"="Product"."product_ref_id"
474
- INNER JOIN "bizplaces" "Bizplace" ON "Bizplace"."id"="Inventory"."bizplace_id"
475
- INNER JOIN "locations" "Location" ON "Location"."id" = "Inventory"."location_id"
476
- LEFT JOIN "order_inventories_by_products" "OrderInventoriesByProduct"
477
- ON "OrderInventoriesByProduct"."product_id" = "Inventory"."product_id"
478
- AND "OrderInventoriesByProduct"."packing_type" = "Inventory"."packing_type"
479
- WHERE "Inventory"."qty" >= 0
480
- AND "Inventory"."status" <> 'MISSING'
481
- AND "Inventory"."domain_id" = $1
482
- ${bizplaceQuery}
483
- ${productQuery}
484
- GROUP BY "Product"."id", "Bizplace"."id", "ProductDetailBizplaceSetting"."id", "Inventory"."packing_type", "Location"."type", "ProductRef"."id"
485
- HAVING 1 = 1
486
- ${availableStockQuery}
487
- )`,
488
- [domain.id]
489
- )
490
-
491
- await tx.query(
492
- `
493
- CREATE TEMP TABLE grouped_inventories_product AS (
494
- SELECT "id", "sku", "brand", "name", "description",
495
- "packingType", "bizplaceId", "bizplaceName", sum("averageUnitCost") AS "averageUnitCost",
496
- sum("minQty") AS "minQty", sum("maxQty") AS "maxQty",
497
- sum("availableQty") AS "availableQty",
498
- sum("quarantineQty") AS "quarantineQty",
499
- sum("reserveQty") AS "reserveQty",
500
- sum("releaseQty") AS "releaseQty",
501
- sum("availableQty" + "quarantineQty" + "releaseQty" + "reserveQty" - "binQty") AS "warehouseQty"
502
- FROM temp_inv_history
503
- GROUP BY "id", "sku", "brand", "name", "description", "packingType", "bizplaceId", "bizplaceName"
504
- HAVING 1=1
505
- ${qtyStockQuery}
506
- ${thresholdQuery}
507
- )
508
- `
509
- )
510
- const results: any = await tx.query(
511
- `
512
- SELECT * FROM grouped_inventories_product
513
- ORDER BY "bizplaceName", "sku"
514
- OFFSET $1 LIMIT $2
515
- `,
516
- [(page - 1) * limit, limit]
517
- )
518
-
519
- const total: any = await tx.query(`SELECT COUNT(*) FROM grouped_inventories_product`)
520
-
521
- await tx.query(`drop table temp_inv_history, order_inventories_by_products, grouped_inventories_product`)
522
-
523
- return {
524
- items: results.map((item: any) => {
525
- return {
526
- product: {
527
- id: item.id,
528
- name: item.name,
529
- sku: item.sku,
530
- brand: item.brand,
531
- description: item.description,
532
- minQty: item.minQty,
533
- maxQty: item.maxQty
534
- },
535
- bizplace: {
536
- id: item.bizplaceId,
537
- name: item.bizplaceName
538
- },
539
- packingType: item.packingType,
540
- averageUnitCost: item.averageUnitCost,
541
- availableQty: item.availableQty,
542
- releaseQty: item.releaseQty,
543
- quarantineQty: item.quarantineQty,
544
- reserveQty: item.reserveQty,
545
- warehouseQty: item.warehouseQty
546
- }
547
- }),
548
- total: total[0].count
549
- }
550
- } catch (error) {
551
- throw error
552
- }
553
- }
554
-
555
- @Directive('@transaction')
556
- @Query(returns => InventoryList)
557
- async inventoriesByStrategy(
558
- @Ctx() context: ResolverContext,
559
- @Arg('worksheetId') worksheetId: string,
560
- @Arg('batchId') batchId: string,
561
- @Arg('productName') productName: string,
562
- @Arg('productSku') productSku: string,
563
- @Arg('packingType') packingType: string,
564
- @Arg('packingSize') packingSize: number,
565
- @Arg('uom') uom: string,
566
- @Arg('pickingStrategy') pickingStrategy: string,
567
- @Arg('locationSortingRules', type => [Sorting], { nullable: true }) locationSortingRules?: Sorting[],
568
- @Arg('bizplaceId', { nullable: true }) bizplaceId?: string
569
- ): Promise<InventoryList> {
570
- const { domain, tx } = context.state
571
-
572
- const inventoryAssignmentSetting: Setting = await tx.getRepository(Setting).findOne({
573
- where: { domain: { id: domain.id }, name: 'rule-for-inventory-assignment' }
574
- })
575
-
576
- if (!locationSortingRules && inventoryAssignmentSetting) {
577
- locationSortingRules = []
578
- let locationSetting = JSON.parse(inventoryAssignmentSetting.value)
579
- for (const key in locationSetting) {
580
- locationSortingRules.push({ name: key, desc: locationSetting[key] == 'ASC' ? false : true })
581
- }
582
- }
583
-
584
- return inventoriesByStrategy(
585
- {
586
- worksheetId,
587
- batchId,
588
- bizplaceId,
589
- productName,
590
- productSku,
591
- packingType,
592
- packingSize,
593
- uom,
594
- pickingStrategy,
595
- locationSortingRules
596
- },
597
- tx
598
- )
599
- }
600
-
601
- @Directive('@transaction')
602
- @Query(returns => Boolean)
603
- async checkProductIdenticality(
604
- @Arg('palletA') palletA: string,
605
- @Arg('palletB') palletB: string,
606
- @Ctx() context: ResolverContext
607
- ): Promise<Boolean> {
608
- const { tx } = context.state
609
- const invRepo: Repository<Inventory> = tx.getRepository(Inventory)
610
- const invA: Inventory = await invRepo.findOne({
611
- where: { domain: { id: context.state.domain.id }, palletId: palletA, status: INVENTORY_STATUS.STORED },
612
- relations: ['product']
613
- })
614
-
615
- const invB: Inventory = await invRepo.findOne({
616
- where: { domain: { id: context.state.domain.id }, palletId: palletB, status: INVENTORY_STATUS.STORED },
617
- relations: ['product']
618
- })
619
-
620
- return (
621
- invA?.batchId === invB?.batchId &&
622
- invA?.product?.id === invB?.product?.id &&
623
- invA?.packingType === invB?.packingType
624
- )
625
- }
626
-
627
- @Query(returns => Boolean)
628
- async checkCartonIdenticality(
629
- @Arg('cartonA') cartonA: string,
630
- @Arg('palletA') palletA: string,
631
- @Arg('cartonB') cartonB: string,
632
- @Ctx() context: ResolverContext
633
- ) {
634
- const invRepo: Repository<Inventory> = getRepository(Inventory)
635
- const invA: Inventory = await invRepo.findOne({
636
- where: {
637
- domain: { id: context.state.domain.id },
638
- palletId: palletA,
639
- cartonId: cartonA,
640
- status: INVENTORY_STATUS.STORED
641
- },
642
- relations: ['product']
643
- })
644
-
645
- const invB: Inventory = await invRepo.findOne({
646
- where: {
647
- domain: { id: context.state.domain.id },
648
- packingType: invA.packingType,
649
- packingSize: invA.packingSize,
650
- product: { id: invA.product.id },
651
- cartonId: cartonB,
652
- status: INVENTORY_STATUS.STORED
653
- },
654
- relations: ['product']
655
- })
656
-
657
- return (
658
- invA?.expirationDate === invB?.expirationDate &&
659
- invA?.product?.id === invB?.product?.id &&
660
- invA?.packingType === invB?.packingType &&
661
- invA?.packingSize === invB?.packingSize
662
- )
663
- }
664
-
665
- @Query(returns => Boolean)
666
- async checkInventoryOwner(
667
- @Arg('palletId') palletId: string,
668
- @Arg('bizplaceName') bizplaceName: string,
669
- @Ctx() context: ResolverContext
670
- ): Promise<Boolean> {
671
- const invRepo: Repository<Inventory> = getRepository(Inventory)
672
- const bizRepo: Repository<Bizplace> = getRepository(Bizplace)
673
-
674
- const inventory: Inventory = await invRepo.findOne({
675
- where: { domain: { id: context.state.domain.id }, palletId, status: INVENTORY_STATUS.STORED },
676
- relations: ['bizplace']
677
- })
678
-
679
- if (!inventory) throw new Error('This inventory status is not stored.')
680
-
681
- const ownerBizplace: Bizplace = await bizRepo.findOne({
682
- where: { name: bizplaceName }
683
- })
684
-
685
- const foundBizplace: Bizplace = inventory.bizplace
686
-
687
- return Boolean(ownerBizplace.id === foundBizplace.id)
688
- }
689
-
690
- @Directive('@privilege(category: "inventory", privilege: "query", domainOwnerGranted: true)')
691
- @Query(returns => InventoryBundleGroupDetail)
692
- async inventoriesByBundle(
693
- @Ctx() context: ResolverContext,
694
- @Arg('filters', type => [Filter], { nullable: true }) filters?: Filter[],
695
- @Arg('pagination', type => Pagination, { nullable: true }) pagination?: Pagination,
696
- @Arg('sortings', type => [Sorting], { nullable: true }) sortings?: Sorting[]
697
- ): Promise<InventoryBundleGroupDetail> {
698
- const { domain, user } = context.state
699
-
700
- const params = { filters, pagination }
701
-
702
- const productBundleId = params.filters.filter(x => x.name === 'productBundleId')[0].value
703
- if (!productBundleId) {
704
- throw new Error('params product bundle is missing')
705
- }
706
-
707
- const bundleReleaseQty: number = params.filters.filter(x => x.name === 'bundleReleaseQty')[0].value
708
-
709
- // remove productBundleId and bundleReleaseQty from params.filters
710
- params.filters = params.filters.filter(x => x.name !== 'productBundleId' && x.name !== 'bundleReleaseQty')
711
-
712
- if (!params.filters.find((filter: any) => filter.name === 'bizplace_id')) {
713
- params.filters.push({
714
- name: 'bizplace_id',
715
- operator: 'in',
716
- value: await getPermittedBizplaceIds(domain, user),
717
- relation: true
718
- })
719
- }
720
-
721
- const remainOnlyParam = params?.filters?.find(
722
- (f: { name: string; operator: string; value: any }) => f.name === 'remainOnly'
723
- )
724
-
725
- let remainOnly: boolean = false
726
- if (typeof remainOnlyParam?.value !== 'undefined') {
727
- remainOnly = remainOnlyParam.value
728
- params.filters = params.filters.filter(
729
- (f: { name: string; operator: string; value: any }) => f.name !== 'remainOnly'
730
- )
731
- }
732
-
733
- const unlockOnlyParam = params?.filters?.find(
734
- (f: { name: string; operator: string; value: any }) => f.name === 'unlockOnly'
735
- )
736
-
737
- let unlockOnly: boolean = false
738
- if (typeof unlockOnlyParam?.value !== 'undefined') {
739
- unlockOnly = unlockOnlyParam.value
740
- params.filters = params.filters.filter(
741
- (f: { name: string; operator: string; value: any }) => f.name !== 'unlockOnly'
742
- )
743
- }
744
-
745
- const productBundleSettings: ProductBundleSetting[] = await getRepository(ProductBundleSetting).find({
746
- where: { productBundle: { id: productBundleId } },
747
- relations: ['product', 'productBundle']
748
- })
749
-
750
- if (!productBundleSettings.length) {
751
- throw new Error('product bundle settings is not found')
752
- }
753
-
754
- const qb: SelectQueryBuilder<Inventory> = getRepository(Inventory).createQueryBuilder('iv')
755
- buildQuery(qb, params, context)
756
-
757
- qb.select('iv.product_id', 'productId')
758
- .addSelect('iv.batch_id', 'batchId')
759
- .addSelect('iv.batch_id_ref', 'batchIdRef')
760
- .addSelect('iv.packing_type', 'packingType')
761
- .addSelect('SUM(iv.qty)', 'qty')
762
- .addSelect('SUM(iv.locked_qty)', 'lockedQty')
763
- .addSelect('SUM(iv.uom_value)', 'uomValue')
764
- .addSelect('SUM(iv.locked_uom_value)', 'lockedUomValue')
765
- .addSelect('prd.sku', 'productSku')
766
- .addSelect('prd.name', 'productName')
767
- .addSelect('prd.description', 'productDescription')
768
- .addSelect('prd.type', 'productType')
769
- .addSelect('prd.primary_unit', 'uom')
770
- .leftJoin('iv.product', 'prd')
771
-
772
- if (remainOnly) {
773
- qb.andWhere('iv.qty > 0')
774
- .andWhere('CASE WHEN iv.lockedQty IS NULL THEN 0 ELSE iv.lockedQty END >= 0')
775
- .andWhere('iv.qty - CASE WHEN iv.lockedQty IS NULL THEN 0 ELSE iv.lockedQty END > 0')
776
- }
777
-
778
- if (unlockOnly) {
779
- qb.andWhere('CASE WHEN iv.lockedQty IS NULL THEN 0 ELSE iv.lockedQty END = 0')
780
- }
781
-
782
- qb.andWhere('iv.product_id IN (:...productIds)', {
783
- productIds: productBundleSettings.map(productBundle => productBundle.product.id)
784
- })
785
- .groupBy('iv.product_id')
786
- .addGroupBy('iv.batch_id')
787
- .addGroupBy('iv.batch_id_ref')
788
- .addGroupBy('iv.packing_type')
789
- .addGroupBy('prd.sku')
790
- .addGroupBy('prd.name')
791
- .addGroupBy('prd.description')
792
- .addGroupBy('prd.type')
793
- .addOrderBy('prd.name')
794
- .addGroupBy('prd.primary_unit')
795
- .addOrderBy('iv.batch_id')
796
-
797
- let bundleGroup = await qb.getRawMany()
798
-
799
- bundleGroup = bundleGroup.map((item: any) => {
800
- return {
801
- ...item,
802
- bundleId: productBundleId,
803
- remainQty: item.qty - (item.lockedQty ? item.lockedQty : 0),
804
- remainUomValue: item.uomValue - (item.lockedUomValue ? item.lockedUomValue : 0),
805
- uom: item.uom,
806
- bundleQty: productBundleSettings.filter(pbs => pbs.product.id === item.productId)[0].bundleQty
807
- }
808
- })
809
-
810
- const bundleSetting = productBundleSettings.map(pbs => {
811
- return {
812
- id: pbs.id,
813
- productId: pbs.product.id,
814
- bundleId: pbs.productBundle.id,
815
- bundleQty: pbs.bundleQty,
816
- releaseQty: bundleReleaseQty * pbs.bundleQty,
817
- productReleaseQty: 0,
818
- productReleaseUomValue: 0
819
- }
820
- })
821
-
822
- /** ************************ **/
823
- bundleGroup = bundleGroup
824
- .map(group => {
825
- const uomValue = group.remainUomValue / group.remainQty
826
-
827
- bundleSetting.forEach(setting => {
828
- let assigned = false
829
- let releaseQty = 0
830
- let releaseUomValue = 0
831
-
832
- if (group.productId === setting.productId) {
833
- if (setting.productReleaseQty < setting.releaseQty) {
834
- assigned = true
835
- const leftQty = setting.releaseQty - setting.productReleaseQty
836
- releaseQty = leftQty > group.remainQty ? group.remainQty : leftQty
837
- releaseUomValue = Math.round(releaseQty * uomValue * 100) / 100
838
- setting.productReleaseQty += releaseQty
839
- setting.productReleaseUomValue += releaseUomValue
840
- }
841
-
842
- group.assigned = assigned
843
- group.releaseQty = releaseQty
844
- group.releaseUomValue = releaseUomValue
845
- group.releaseUomValueWithUom = `${releaseUomValue} ${group.uom}`
846
- }
847
- })
848
-
849
- return group
850
- })
851
- .filter(group => group.assigned)
852
- /** ************************ **/
853
-
854
- return { bundleGroup, bundleSetting }
855
- }
856
-
857
- @Directive('@privilege(category: "inventory", privilege: "query", domainOwnerGranted: true)')
858
- @Directive('@transaction')
859
- @Query(returns => InventoryList)
860
- async inventoriesGroupByProduct(
861
- @Args(type => ListParam) params: ListParam,
862
- @Ctx() context: ResolverContext
863
- ): Promise<InventoryList> {
864
- try {
865
- const { domain, user, tx } = context.state
866
- let permittedBizplaceIds: string[] = await getPermittedBizplaceIds(domain, user)
867
- const partnersCompanyBizplaces: Bizplace[] = await getPartnersCompanyBizplaces(domain, user)
868
-
869
- const page = params.pagination.page
870
- const limit = params.pagination.limit
871
-
872
- let bizplaceFilter = params.filters.find(filter => filter.name == 'bizplace')
873
- let productFilter = params.filters.find(filter => filter.name == 'product')
874
- let productTypeFilter = params.filters.find(filter => filter.name == 'type')
875
- let availableStockFilter = params.filters.find(filter => filter.name == 'availableStock')
876
- let lowStockFilter = params.filters.find(filter => filter.name == 'lowStock')
877
- let overStockFilter = params.filters.find(filter => filter.name == 'overStock')
878
- let warehouseNameFilter = params.filters.find(filter => filter.name == 'warehouseName')
879
- let unexpiredOnlyFilter = params.filters.find(filter => filter.name === 'unexpiredOnly')
880
-
881
- if (bizplaceFilter) {
882
- const bizplaceQueryBuilder: SelectQueryBuilder<Bizplace> = getRepository(Bizplace).createQueryBuilder('b')
883
- bizplaceQueryBuilder
884
- .innerJoin('companies', 'c2', ' c2.domain_id = b.domain_id')
885
- .innerJoin('bizplaces', 'b2', 'b2.company_id = c2.id')
886
- .where('b2.id = :bizplaceId', { bizplaceId: bizplaceFilter.value })
887
-
888
- let bizplaceList = await bizplaceQueryBuilder.getMany()
889
-
890
- permittedBizplaceIds = [
891
- ...permittedBizplaceIds.filter(itm => itm == bizplaceFilter.value),
892
- ...bizplaceList.map(bz => bz.id),
893
- ...partnersCompanyBizplaces.map(biz => biz.id)
894
- ]
895
- }
896
-
897
- let bizplaceQuery = ''
898
- if (permittedBizplaceIds.length > 0) {
899
- bizplaceQuery = `AND EXISTS (
900
- SELECT * FROM (VALUES ${permittedBizplaceIds.map(id => `('${id}')`).join(',')})
901
- AS bizFilter(bizplace_id)
902
- WHERE bizFilter.bizplace_id::uuid = "Inventory"."bizplace_id"
903
- )`
904
- } else {
905
- bizplaceQuery = `1 = 0`
906
- }
907
-
908
- let productQuery = ''
909
- if (productFilter) {
910
- let productValue = productFilter.value
911
- .toLowerCase()
912
- .split(',')
913
- .map(prod => {
914
- return "'%" + prod.trim().replace(/'/g, "''") + "%'"
915
- })
916
- .join(',')
917
- productQuery = `AND (
918
- Lower("Product"."name") LIKE ANY(ARRAY[${productValue}])
919
- OR Lower("Product"."sku") LIKE ANY(ARRAY[${productValue}])
920
- OR Lower("Product"."brand") LIKE ANY(ARRAY[${productValue}])
921
- OR Lower("Product"."description") LIKE ANY(ARRAY[${productValue}])
922
- OR Lower("ProductRef"."name") LIKE ANY(ARRAY[${productValue}])
923
- OR Lower("ProductRef"."sku") LIKE ANY(ARRAY[${productValue}])
924
- OR Lower("ProductRef"."description") LIKE ANY(ARRAY[${productValue}])
925
- )`
926
- }
927
-
928
- if (productTypeFilter) {
929
- let productTypeValue = productTypeFilter.value
930
- .toLowerCase()
931
- .split(',')
932
- .map(prod => {
933
- return "'%" + prod.trim().replace(/'/g, "''") + "%'"
934
- })
935
- .join(',')
936
- productQuery =
937
- productQuery +
938
- ` AND (
939
- Lower("Product"."type") LIKE ANY(ARRAY[${productTypeValue}])
940
- )`
941
- }
942
-
943
- let availableStockQuery = ''
944
- if (availableStockFilter?.value) {
945
- availableStockQuery = ` AND SUM("Inventory"."qty") > 0`
946
- }
947
-
948
- let unexpiredOnlyQuery = ''
949
- if (unexpiredOnlyFilter?.value) {
950
- unexpiredOnlyQuery = 'AND "Inventory"."expiration_date" >= CURRENT_DATE'
951
- }
952
-
953
- let thresholdQuery = ''
954
- if (lowStockFilter?.value) {
955
- thresholdQuery = `AND COALESCE("ProductDetailBizplaceSetting"."min_qty", "Product"."min_qty",0) > SUM("Inventory"."qty")`
956
- }
957
-
958
- if (overStockFilter?.value) {
959
- thresholdQuery = `AND COALESCE("ProductDetailBizplaceSetting"."max_qty", "Product"."max_qty",0) > 0 AND
960
- COALESCE("ProductDetailBizplaceSetting"."max_qty", "Product"."max_qty",0) < SUM("Inventory"."qty")`
961
- }
962
-
963
- let warehouseQuery = ''
964
- if (warehouseNameFilter?.value) {
965
- warehouseQuery = `AND "Warehouse"."name" like '%${warehouseNameFilter.value}%'`
966
- }
967
-
968
- await tx.query(
969
- `
970
- create temp table temp_inv_history AS
971
- (
972
- SELECT "Product"."id" AS "id", "Product"."sku" AS "sku", "Product"."brand" AS "brand", "Product"."name" AS "name", "Product"."type" AS "type", "Product"."description" AS "description", "Product"."primary_unit" AS "primaryUnit",
973
- "Product"."weight" AS "weight", "ProductRef"."id" AS "productRefId",
974
- "ProductRef"."description" AS "productRefDesciption", "Bizplace"."id" AS "bizplaceId", "Bizplace"."name" AS "bizplaceName", SUM("Inventory"."qty") AS "qty" , SUM("Inventory"."uom_value") AS "uomValue",
975
- "Warehouse"."id" AS "warehouseId", "Warehouse"."name" AS "warehouseName",
976
- CASE WHEN SUM("Inventory"."qty") > 0 THEN SUM(COALESCE("Inventory"."unit_cost", 0) * "Inventory"."qty")/ SUM("Inventory"."qty") ELSE 0 END AS "averageUnitCost",
977
- COALESCE("ProductDetailBizplaceSetting"."min_qty", "Product"."min_qty",0) AS "minQty",
978
- COALESCE("ProductDetailBizplaceSetting"."max_qty", "Product"."max_qty",0) AS "maxQty"
979
- FROM "inventories" "Inventory"
980
- LEFT JOIN "warehouses" "Warehouse" ON "Warehouse"."id"="Inventory"."warehouse_id"
981
- LEFT JOIN "products" "Product" ON "Product"."id"="Inventory"."product_id"
982
- LEFT JOIN "product_details" "ProductDetails" ON "ProductDetails"."product_id" = "Product"."id"
983
- AND "ProductDetails"."is_default"
984
- LEFT JOIN "product_detail_bizplace_settings" "ProductDetailBizplaceSetting" ON "ProductDetailBizplaceSetting"."product_detail_id" = "ProductDetails"."id"
985
- AND "ProductDetailBizplaceSetting"."domain_id" = "Inventory"."domain_id"
986
- LEFT JOIN "products" "ProductRef" ON "ProductRef"."id"="Product"."product_ref_id"
987
- INNER JOIN "bizplaces" "Bizplace" ON "Bizplace"."id"="Inventory"."bizplace_id"
988
- WHERE "Inventory"."qty" >= 0
989
- AND "Inventory"."domain_id" = $1
990
- ${bizplaceQuery}
991
- ${productQuery}
992
- ${warehouseQuery}
993
- ${unexpiredOnlyQuery}
994
- GROUP BY "Product"."id", "ProductRef"."id", "Bizplace"."id", "ProductDetailBizplaceSetting"."id", "Warehouse"."id"
995
- HAVING 1 = 1
996
- ${availableStockQuery}
997
- ${thresholdQuery}
998
- )`,
999
- [domain.id]
1000
- )
1001
-
1002
- const results: any = await tx.query(
1003
- `
1004
- SELECT * FROM temp_inv_history
1005
- ORDER BY "bizplaceName", "sku"
1006
- OFFSET $1 LIMIT $2
1007
- `,
1008
- [(page - 1) * limit, limit]
1009
- )
1010
-
1011
- const total: any = await tx.query(`SELECT COUNT(*) FROM temp_inv_history`)
1012
-
1013
- tx.query(`drop table temp_inv_history`)
1014
-
1015
- return {
1016
- items: results.map((item: any) => {
1017
- return {
1018
- product: {
1019
- id: item.id,
1020
- name: item.name,
1021
- sku: item.sku,
1022
- brand: item.brand,
1023
- description: item.description,
1024
- type: item.type,
1025
- weight: item.weight,
1026
- productRefId: item.productRefId,
1027
- bizplaceId: item.bizplaceId,
1028
- minQty: item.minQty,
1029
- maxQty: item.maxQty,
1030
- primaryUnit: item.primaryUnit
1031
- },
1032
- warehouse: {
1033
- id: item.warehouseId,
1034
- name: item.warehouseName
1035
- },
1036
- averageUnitCost: item.averageUnitCost,
1037
- qty: item.qty,
1038
- uomValue: item.uomValue
1039
- }
1040
- }),
1041
- total: total[0].count
1042
- }
1043
- } catch (error) {
1044
- throw error
1045
- }
1046
- }
1047
-
1048
- @FieldResolver(type => Domain)
1049
- async domain(@Root() inventory: Inventory): Promise<Domain> {
1050
- return await getRepository(Domain).findOneBy({ id: inventory.domainId })
1051
- }
1052
-
1053
- @FieldResolver(type => User)
1054
- async updater(@Root() inventory: Inventory): Promise<User> {
1055
- return await getRepository(User).findOneBy({ id: inventory.updaterId })
1056
- }
1057
-
1058
- @FieldResolver(type => User)
1059
- async creator(@Root() inventory: Inventory): Promise<User> {
1060
- return await getRepository(User).findOneBy({ id: inventory.creatorId })
1061
- }
1062
-
1063
- /*
1064
- move changeCount to @FieldResolver so that system don't have
1065
- to keep searching for this when user does not request for it
1066
- */
1067
- @FieldResolver(type => Number)
1068
- async changeCount(@Root() inventory: Inventory): Promise<Number> {
1069
- return await getRepository(InventoryChange).count({
1070
- where: { inventory: { id: inventory.id } }
1071
- })
1072
- }
1073
-
1074
- /*
1075
- "purchaseOrderNo", "orderProductRemark" and "arrivalNoticeRefNo"
1076
- are purely for Getha's lot label. @farishelmi added these here to
1077
- avoid logic changes in @inventories query
1078
- */
1079
- @FieldResolver(type => String)
1080
- async purchaseOrderNo(@Root() inventory: Inventory): Promise<String> {
1081
- if (!inventory.refOrderId) return ''
1082
-
1083
- let items: any = await getRepository(Inventory).query(`
1084
- SELECT po.name as "purchaseOrderNo" FROM arrival_notices an
1085
- LEFT JOIN purchase_orders po
1086
- ON an.purchase_order_id = po.id
1087
- WHERE an.id = '${inventory.refOrderId}'
1088
- LIMIT 1
1089
- `)
1090
-
1091
- return items[0]?.purchaseOrderNo || ''
1092
- }
1093
-
1094
- @FieldResolver(type => String)
1095
- async orderProductRemark(@Root() inventory: Inventory): Promise<String> {
1096
- if (!inventory.orderProductId) return ''
1097
-
1098
- let items: any = await getRepository(Inventory).query(`
1099
- SELECT op.remark as "orderProductRemark" FROM order_products op
1100
- WHERE op.id = '${inventory.orderProductId}'
1101
- LIMIT 1
1102
- `)
1103
-
1104
- return items[0]?.orderProductRemark || ''
1105
- }
1106
-
1107
- @FieldResolver(type => String)
1108
- async arrivalNoticeRefNo(@Root() inventory: Inventory): Promise<String> {
1109
- if (!inventory.refOrderId) return ''
1110
-
1111
- let items: any = await getRepository(Inventory).query(`
1112
- SELECT an.ref_no as "arrivalNoticeRefNo" FROM arrival_notices an
1113
- WHERE an.id = '${inventory.refOrderId}'
1114
- LIMIT 1
1115
- `)
1116
-
1117
- return items[0]?.arrivalNoticeRefNo || ''
1118
- }
1119
-
1120
- @FieldResolver(type => String)
1121
- async arrivalNoticeNo(@Root() inventory: Inventory): Promise<String> {
1122
- if (!inventory.refOrderId) return ''
1123
-
1124
- let items: any = await getRepository(Inventory).query(`
1125
- SELECT an.name as "arrivalNoticeNo" FROM arrival_notices an
1126
- WHERE an.id = '${inventory.refOrderId}'
1127
- LIMIT 1
1128
- `)
1129
-
1130
- return items[0]?.arrivalNoticeNo || ''
1131
- }
1132
- }
1133
-
1134
- export async function inventoriesByStrategy(
1135
- {
1136
- worksheetId,
1137
- batchId,
1138
- bizplaceId,
1139
- productName,
1140
- productSku,
1141
- packingType,
1142
- packingSize,
1143
- uom,
1144
- pickingStrategy,
1145
- locationSortingRules
1146
- },
1147
- trxMgr: EntityManager
1148
- ) {
1149
- const qb = await trxMgr.getRepository(Inventory).createQueryBuilder('INV')
1150
- qb.innerJoinAndSelect('INV.product', 'PROD')
1151
- .innerJoinAndSelect('INV.location', 'LOC')
1152
- .addSelect(subQuery =>
1153
- subQuery
1154
- .select('COALESCE(SUM(release_qty), 0)', 'releaseQty')
1155
- .from('order_inventories', 'OI')
1156
- .where('"OI"."inventory_id" = "INV"."id"')
1157
- .andWhere("\"OI\".\"status\" IN ('PENDING', 'PENDING_RECEIVE', 'PENDING_WORKSHEET', 'PENDING_SPLIT')")
1158
- .andWhere('"OI"."ref_worksheet_id" <> :worksheetId', { worksheetId: worksheetId })
1159
- )
1160
- .addSelect(subQuery =>
1161
- subQuery
1162
- .select('COALESCE(SUM(release_uom_value), 0)', 'releaseUomValue')
1163
- .from('order_inventories', 'OI')
1164
- .where('"OI"."inventory_id" = "INV"."id"')
1165
- .andWhere("\"OI\".\"status\" IN ('PENDING', 'PENDING_RECEIVE', 'PENDING_WORKSHEET', 'PENDING_SPLIT')")
1166
- .andWhere('"OI"."ref_worksheet_id" <> :worksheetId', { worksheetId: worksheetId })
1167
- )
1168
- .andWhere('"PROD"."name" = :productName')
1169
- .andWhere('"PROD"."sku" = :productSku')
1170
- .andWhere('"INV"."packing_type" = :packingType')
1171
- .andWhere('"INV"."packing_size" = :packingSize')
1172
- .andWhere('"INV"."uom" = :uom')
1173
- .andWhere('"INV"."status" = :status', { status: 'STORED' })
1174
- .andWhere('"LOC"."type" NOT IN (:...locationTypes)', {
1175
- locationTypes: [LOCATION_TYPE.QUARANTINE, LOCATION_TYPE.RESERVE]
1176
- })
1177
- .setParameters({
1178
- productName,
1179
- productSku,
1180
- packingType,
1181
- packingSize,
1182
- uom
1183
- })
1184
-
1185
- if (batchId !== '') {
1186
- qb.andWhere('"INV"."batch_id" = :batchId', { batchId })
1187
- }
1188
-
1189
- if (bizplaceId) {
1190
- qb.andWhere('"INV"."bizplace_id" = :bizplaceId', { bizplaceId: bizplaceId })
1191
- }
1192
-
1193
- switch (pickingStrategy.toUpperCase()) {
1194
- case 'FIFO':
1195
- qb.orderBy('"INV"."created_at"', 'ASC')
1196
- if (locationSortingRules?.length > 0) {
1197
- locationSortingRules.forEach((rule: { name: string; desc: boolean }, idx: number) => {
1198
- qb.addOrderBy(`LOC.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
1199
- })
1200
- }
1201
- break
1202
-
1203
- case 'LIFO':
1204
- qb.orderBy('"INV"."created_at"', 'DESC')
1205
- if (locationSortingRules?.length > 0) {
1206
- locationSortingRules.forEach((rule: { name: string; desc: boolean }, idx: number) => {
1207
- qb.addOrderBy(`LOC.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
1208
- })
1209
- }
1210
- break
1211
-
1212
- case 'FEFO':
1213
- qb.orderBy('"INV"."expiration_date"', 'ASC')
1214
- qb.addOrderBy('"INV"."created_at"', 'ASC')
1215
- if (locationSortingRules?.length > 0) {
1216
- locationSortingRules.forEach((rule: { name: string; desc: boolean }, idx: number) => {
1217
- qb.addOrderBy(`LOC.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
1218
- })
1219
- }
1220
- break
1221
-
1222
- case 'FMFO':
1223
- qb.orderBy('"INV"."manufacture_date"', 'ASC')
1224
- qb.addOrderBy('"INV"."created_at"', 'ASC')
1225
- if (locationSortingRules?.length > 0) {
1226
- locationSortingRules.forEach((rule: { name: string; desc: boolean }, idx: number) => {
1227
- qb.addOrderBy(`LOC.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
1228
- })
1229
- }
1230
- break
1231
-
1232
- case 'LOCATION':
1233
- if (locationSortingRules?.length > 0) {
1234
- locationSortingRules.forEach((rule: { name: string; desc: boolean }, idx: number) => {
1235
- idx === 0
1236
- ? qb.orderBy(`LOC.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
1237
- : qb.addOrderBy(`LOC.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
1238
- })
1239
- } else qb.orderBy('"LOC"."name"', 'DESC')
1240
- break
1241
- }
1242
-
1243
- const { entities, raw } = await qb.getRawAndEntities()
1244
- const items = entities
1245
- .map((inv: Inventory, idx: number) => {
1246
- const qty: number = inv?.qty > 0 ? inv.qty : 0
1247
- const uomValue: number = inv?.uomValue > 0 ? inv.uomValue : 0
1248
- const lockedQty: number = inv.lockedQty || 0
1249
- const lockedUomValue: number = inv.lockedUomValue || 0
1250
- const releaseQty: number = parseInt(raw[idx].releaseQty) || 0
1251
- const releaseUomValue: number = parseFloat(raw[idx].releaseUomValue) || 0
1252
-
1253
- return {
1254
- ...inv,
1255
- qty: qty - lockedQty - releaseQty,
1256
- uomValue: uomValue - lockedUomValue - releaseUomValue
1257
- }
1258
- })
1259
- .filter((inv: Inventory) => inv.qty)
1260
-
1261
- const total: number = await qb.getCount()
1262
- return { items, total }
1263
- }