@things-factory/warehouse-base 4.1.41 → 4.2.2

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 (49) hide show
  1. package/dist-server/constants/inventory.js +9 -2
  2. package/dist-server/constants/inventory.js.map +1 -1
  3. package/dist-server/service/index.js +6 -0
  4. package/dist-server/service/index.js.map +1 -1
  5. package/dist-server/service/inventory/inventory-query.js +37 -14
  6. package/dist-server/service/inventory/inventory-query.js.map +1 -1
  7. package/dist-server/service/inventory/inventory-types.js +12 -0
  8. package/dist-server/service/inventory/inventory-types.js.map +1 -1
  9. package/dist-server/service/inventory/inventory.js +22 -0
  10. package/dist-server/service/inventory/inventory.js.map +1 -1
  11. package/dist-server/service/inventory-change/inventory-change-mutation.js +134 -5
  12. package/dist-server/service/inventory-change/inventory-change-mutation.js.map +1 -1
  13. package/dist-server/service/inventory-change/inventory-change-query.js +1 -0
  14. package/dist-server/service/inventory-change/inventory-change-query.js.map +1 -1
  15. package/dist-server/service/inventory-change/inventory-change.js +10 -0
  16. package/dist-server/service/inventory-change/inventory-change.js.map +1 -1
  17. package/dist-server/service/inventory-item/inventory-item-query.js +82 -4
  18. package/dist-server/service/inventory-item/inventory-item-query.js.map +1 -1
  19. package/dist-server/service/inventory-item/inventory-item.js +8 -0
  20. package/dist-server/service/inventory-item/inventory-item.js.map +1 -1
  21. package/dist-server/service/inventory-item-change/index.js +9 -0
  22. package/dist-server/service/inventory-item-change/index.js.map +1 -0
  23. package/dist-server/service/inventory-item-change/inventory-item-change-mutation.js +121 -0
  24. package/dist-server/service/inventory-item-change/inventory-item-change-mutation.js.map +1 -0
  25. package/dist-server/service/inventory-item-change/inventory-item-change-query.js +87 -0
  26. package/dist-server/service/inventory-item-change/inventory-item-change-query.js.map +1 -0
  27. package/dist-server/service/inventory-item-change/inventory-item-change-type.js +107 -0
  28. package/dist-server/service/inventory-item-change/inventory-item-change-type.js.map +1 -0
  29. package/dist-server/service/inventory-item-change/inventory-item-change.js +110 -0
  30. package/dist-server/service/inventory-item-change/inventory-item-change.js.map +1 -0
  31. package/dist-server/utils/inventory-util.js +23 -40
  32. package/dist-server/utils/inventory-util.js.map +1 -1
  33. package/package.json +6 -6
  34. package/server/constants/inventory.ts +9 -1
  35. package/server/service/index.ts +9 -0
  36. package/server/service/inventory/inventory-query.ts +53 -14
  37. package/server/service/inventory/inventory-types.ts +9 -0
  38. package/server/service/inventory/inventory.ts +17 -0
  39. package/server/service/inventory-change/inventory-change-mutation.ts +177 -6
  40. package/server/service/inventory-change/inventory-change-query.ts +1 -0
  41. package/server/service/inventory-change/inventory-change.ts +5 -0
  42. package/server/service/inventory-item/inventory-item-query.ts +98 -3
  43. package/server/service/inventory-item/inventory-item.ts +6 -0
  44. package/server/service/inventory-item-change/index.ts +6 -0
  45. package/server/service/inventory-item-change/inventory-item-change-mutation.ts +116 -0
  46. package/server/service/inventory-item-change/inventory-item-change-query.ts +43 -0
  47. package/server/service/inventory-item-change/inventory-item-change-type.ts +68 -0
  48. package/server/service/inventory-item-change/inventory-item-change.ts +92 -0
  49. package/server/utils/inventory-util.ts +21 -43
@@ -10,7 +10,9 @@ export const INVENTORY_STATUS = {
10
10
  DELETED: 'DELETED',
11
11
  CHECKED: 'CHECKED',
12
12
  PICKED: 'PICKED',
13
- PICKING: 'PICKING'
13
+ PICKING: 'PICKING',
14
+ SORTING: 'SORTING',
15
+ SORTED: 'SORTED'
14
16
  }
15
17
 
16
18
  export const INVENTORY_TYPES = {
@@ -56,3 +58,9 @@ export const INVENTORY_ITEM_SOURCE = {
56
58
  OUTBOUND: 'OUTBOUND',
57
59
  ADJUSTMENT: 'ADJUSTMENT'
58
60
  }
61
+
62
+ export const INVENTORY_ITEM_CHANGE_TYPE = {
63
+ NEW: 'NEW',
64
+ MODIFIED: 'MODIFIED',
65
+ REMOVED: 'REMOVED'
66
+ }
@@ -2,6 +2,10 @@ import { entities as InventoryEntities, resolvers as InventoryResolvers } from '
2
2
  import { entities as InventoryChangeEntities, resolvers as InventoryChangeResolvers } from './inventory-change'
3
3
  import { entities as InventoryHistoryEntities, resolvers as InventoryHistoryResvolers } from './inventory-history'
4
4
  import { entities as InventoryItemEntities, resolvers as InventoryItemResolvers } from './inventory-item'
5
+ import {
6
+ entities as InventoryItemChangeEntities,
7
+ resolvers as InventoryItemChangeResolvers
8
+ } from './inventory-item-change'
5
9
  import { entities as LocationEntities, resolvers as LocationResolvers } from './location'
6
10
  import { entities as MovementEntities, resolvers as MovementResolvers } from './movement'
7
11
  import { entities as PalletEntities, resolvers as PalletResolvers } from './pallet'
@@ -11,7 +15,9 @@ import { entities as ReducedInventoryHistoryEntities } from './reduced-inventory
11
15
  import { entities as WarehouseEntities, resolvers as WarehouseResolvers } from './warehouse'
12
16
 
13
17
  /* EXPORT ENTITY TYPES */
18
+ export * from './inventory-item-change/inventory-item-change'
14
19
  export * from './inventory-item/inventory-item'
20
+ export * from './inventory-item-change/inventory-item-change'
15
21
  export * from './inventory/inventory'
16
22
  export * from './inventory-change/inventory-change'
17
23
  export * from './inventory-history/inventory-history'
@@ -27,6 +33,7 @@ export * from './reduced-inventory-history/reduced-inventory-history'
27
33
  export * from './inventory/inventory-types'
28
34
  export * from './inventory/inventory-types'
29
35
  export * from './inventory-item/inventory-item'
36
+ export * from './inventory-item-change/inventory-item-change'
30
37
  export * from './inventory-change/inventory-change-types'
31
38
  export * from './inventory-history/inventory-history-types'
32
39
  export * from './location/location-types'
@@ -41,6 +48,7 @@ export const entities = [
41
48
  ...InventoryChangeEntities,
42
49
  ...InventoryHistoryEntities,
43
50
  ...InventoryItemEntities,
51
+ ...InventoryItemChangeEntities,
44
52
  ...LocationEntities,
45
53
  ...MovementEntities,
46
54
  ...PalletEntities,
@@ -56,6 +64,7 @@ export const schema = {
56
64
  ...InventoryChangeResolvers,
57
65
  ...InventoryHistoryResvolers,
58
66
  ...InventoryItemResolvers,
67
+ ...InventoryItemChangeResolvers,
59
68
  ...LocationResolvers,
60
69
  ...MovementResolvers,
61
70
  ...PalletResolvers,
@@ -11,6 +11,7 @@ import { INVENTORY_STATUS, LOCATION_TYPE } from '../../constants'
11
11
  import { InventoryChange } from '../inventory-change/inventory-change'
12
12
  import { Inventory } from './inventory'
13
13
  import { InventoryBundleGroupDetail, InventoryList } from './inventory-types'
14
+ import { InventoryItem } from '../inventory-item/inventory-item'
14
15
 
15
16
  @Resolver(Inventory)
16
17
  export class InventoryQuery {
@@ -70,6 +71,12 @@ export class InventoryQuery {
70
71
  .leftJoinAndSelect('iv.product', 'product')
71
72
  .leftJoinAndSelect('iv.warehouse', 'warehouse')
72
73
  .leftJoinAndSelect('iv.location', 'location')
74
+ .leftJoin('iv.inventoryItems', 'ivi')
75
+ .loadRelationCountAndMap('iv.inventoryItemCount', 'iv.inventoryItems', 'ivic', qb =>
76
+ qb.andWhere('ivic.status = :ivicStatus', {
77
+ ivicStatus: INVENTORY_STATUS.STORED
78
+ })
79
+ )
73
80
 
74
81
  if (remainOnly) {
75
82
  qb.andWhere('iv.qty > 0')
@@ -136,10 +143,22 @@ export class InventoryQuery {
136
143
  }
137
144
  })
138
145
 
146
+ let inventoryItems: InventoryItem[] = await getRepository(InventoryItem).find({
147
+ where: { domain, inventory: item }
148
+ })
149
+
150
+ let serialNumbers: string =
151
+ inventoryItems
152
+ .map(inventoryItem => {
153
+ return inventoryItem.serialNumber
154
+ })
155
+ .join(',') || ''
156
+
139
157
  return {
140
158
  ...item,
141
159
  remainQty: item.qty - selectedQty,
142
- remainUomValue: item.uomValue - selectedUomValue
160
+ remainUomValue: item.uomValue - selectedUomValue,
161
+ serialNumbers: serialNumbers
143
162
  }
144
163
  })
145
164
  )
@@ -745,6 +764,27 @@ export class InventoryQuery {
745
764
  .offset((page - 1) * limit)
746
765
  .limit(limit)
747
766
  .getRawMany()
767
+
768
+ await Promise.all(
769
+ items.map(async item => {
770
+ let inventoryItems: InventoryItem[] = await getRepository(InventoryItem).find({
771
+ where: { domain, inventory: item }
772
+ })
773
+
774
+ let serialNumbers: string =
775
+ inventoryItems
776
+ .map(inventoryItem => {
777
+ return inventoryItem.serialNumber
778
+ })
779
+ .join(',') || ''
780
+
781
+ return {
782
+ ...item,
783
+ serialNumbers: serialNumbers
784
+ }
785
+ })
786
+ )
787
+
748
788
  let total = await qb.getCount()
749
789
  return { items: items.map(item => new Inventory(item)), total }
750
790
  } catch (error) {
@@ -843,9 +883,7 @@ export async function inventoriesByStrategy(
843
883
  qb.orderBy('"INV"."created_at"', 'ASC')
844
884
  if (locationSortingRules?.length > 0) {
845
885
  locationSortingRules.forEach((rule: { name: string; desc: boolean }, idx: number) => {
846
- idx === 0
847
- ? qb.addOrderBy(`LOC.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
848
- : qb.addOrderBy(`LOC.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
886
+ qb.addOrderBy(`LOC.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
849
887
  })
850
888
  }
851
889
  break
@@ -854,9 +892,7 @@ export async function inventoriesByStrategy(
854
892
  qb.orderBy('"INV"."created_at"', 'DESC')
855
893
  if (locationSortingRules?.length > 0) {
856
894
  locationSortingRules.forEach((rule: { name: string; desc: boolean }, idx: number) => {
857
- idx === 0
858
- ? qb.addOrderBy(`LOC.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
859
- : qb.addOrderBy(`LOC.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
895
+ qb.addOrderBy(`LOC.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
860
896
  })
861
897
  }
862
898
  break
@@ -865,9 +901,16 @@ export async function inventoriesByStrategy(
865
901
  qb.orderBy('"INV"."expiration_date"', 'ASC')
866
902
  if (locationSortingRules?.length > 0) {
867
903
  locationSortingRules.forEach((rule: { name: string; desc: boolean }, idx: number) => {
868
- idx === 0
869
- ? qb.addOrderBy(`LOC.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
870
- : qb.addOrderBy(`LOC.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
904
+ qb.addOrderBy(`LOC.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
905
+ })
906
+ }
907
+ break
908
+
909
+ case 'FMFO':
910
+ qb.orderBy('"INV"."manufacture_date"', 'ASC')
911
+ if (locationSortingRules?.length > 0) {
912
+ locationSortingRules.forEach((rule: { name: string; desc: boolean }, idx: number) => {
913
+ qb.addOrderBy(`LOC.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
871
914
  })
872
915
  }
873
916
  break
@@ -880,10 +923,6 @@ export async function inventoriesByStrategy(
880
923
  : qb.addOrderBy(`LOC.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
881
924
  })
882
925
  } else qb.orderBy('"LOC"."name"', 'DESC')
883
- break
884
- default:
885
- qb.orderBy('"INV"."created_at"', 'ASC')
886
-
887
926
  break
888
927
  }
889
928
 
@@ -122,6 +122,12 @@ export class InventoryPatch {
122
122
  @Field(type => Float, { nullable: true })
123
123
  unitCost?: number
124
124
 
125
+ @Field({ nullable: true })
126
+ inventoryItemChangesJson?: string
127
+
128
+ @Field({ nullable: true })
129
+ serialNumbers?: string
130
+
125
131
  @Field({ nullable: true })
126
132
  updatedAt?: string
127
133
 
@@ -202,6 +208,9 @@ export class NewInventory {
202
208
 
203
209
  @Field(type => ScalarDate, { nullable: true })
204
210
  manufactureDate: Date
211
+
212
+ @Field({ nullable: true })
213
+ serialNumbers?: string
205
214
  }
206
215
 
207
216
  @ObjectType()
@@ -17,6 +17,7 @@ import { Product, ProductDetail } from '@things-factory/product-base'
17
17
  import { Domain, ScalarDate } from '@things-factory/shell'
18
18
 
19
19
  import { InventoryChange } from '../inventory-change/inventory-change'
20
+ import { InventoryItem } from '../inventory-item/inventory-item'
20
21
  import { Location } from '../location/location'
21
22
  import { Pallet } from '../pallet/pallet'
22
23
  import { Warehouse } from '../warehouse/warehouse'
@@ -195,6 +196,13 @@ export class Inventory {
195
196
  @Field(type => [InventoryChange], { nullable: true })
196
197
  inventoryChanges: InventoryChange[]
197
198
 
199
+ @OneToMany(type => InventoryItem, inventoryItems => inventoryItems.inventory)
200
+ @Field(type => [InventoryItem], { nullable: true })
201
+ inventoryItems: InventoryItem[]
202
+
203
+ @Field({ nullable: true })
204
+ serialNumbers?: string
205
+
198
206
  @Column('date', { nullable: true })
199
207
  @Field(type => ScalarDate, { nullable: true })
200
208
  expirationDate: Date
@@ -255,6 +263,15 @@ export class Inventory {
255
263
  @Field({ nullable: true })
256
264
  changeCount?: number
257
265
 
266
+ @Field({ nullable: true })
267
+ binLocation?: string
268
+
269
+ @Field({ nullable: true })
270
+ orderNo?: string
271
+
272
+ @Field({ nullable: true })
273
+ inventoryItemCount: number
274
+
258
275
  constructor(obj?) {
259
276
  if (obj) {
260
277
  this.id = obj.Inventory_id
@@ -10,10 +10,13 @@ import { Product } from '@things-factory/product-base'
10
10
  import { PartnerSetting, Setting } from '@things-factory/setting-base'
11
11
  import { Domain } from '@things-factory/shell'
12
12
 
13
- import { INVENTORY_STATUS, LOCATION_STATUS } from '../../constants'
13
+ import { INVENTORY_ITEM_CHANGE_TYPE, INVENTORY_ITEM_SOURCE, INVENTORY_STATUS, LOCATION_STATUS } from '../../constants'
14
14
  import { EcommerceController, SellercraftController } from '../../controllers'
15
15
  import { InventoryNoGenerator } from '../../utils'
16
16
  import { InventoryHistory } from '../inventory-history/inventory-history'
17
+ import { InventoryItemChange } from '../inventory-item-change/inventory-item-change'
18
+ import { InventoryItem } from '../inventory-item/inventory-item'
19
+ import { checkExistingSerialNumber } from '../inventory-item/inventory-item-query'
17
20
  import { Inventory } from '../inventory/inventory'
18
21
  import { InventoryPatch } from '../inventory/inventory-types'
19
22
  import { Location } from '../location/location'
@@ -150,6 +153,7 @@ export class InventoryChangeMutation {
150
153
  }
151
154
 
152
155
  if (foundExistingPallet) {
156
+ delete patches[i].serialNumbers
153
157
  _updateRecords.push(patches[i])
154
158
  } else {
155
159
  _createRecords.push(patches[i])
@@ -157,6 +161,7 @@ export class InventoryChangeMutation {
157
161
  }
158
162
 
159
163
  const inventoryChangeRepo = tx.getRepository(InventoryChange)
164
+ const inventoryItemChangeRepo = tx.getRepository(InventoryItemChange)
160
165
  if (_createRecords.length > 0) {
161
166
  for (let i = 0; i < _createRecords.length; i++) {
162
167
  const newRecord = _createRecords[i]
@@ -182,7 +187,7 @@ export class InventoryChangeMutation {
182
187
  ? null
183
188
  : newRecord.expirationDate
184
189
 
185
- await inventoryChangeRepo.save({
190
+ const newInventoryChange: InventoryChange = await inventoryChangeRepo.save({
186
191
  ...newRecord,
187
192
  id: undefined,
188
193
  name: InventoryNoGenerator.inventoryName(),
@@ -190,6 +195,56 @@ export class InventoryChangeMutation {
190
195
  creator: user,
191
196
  updater: user
192
197
  })
198
+
199
+ if (newRecord?.serialNumbers) {
200
+ let serialNumbers: string = newRecord.serialNumbers
201
+ let newSerialNumbers: any = serialNumbers.split(',')
202
+ let inventoryItemChanges: InventoryItemChange[] = []
203
+
204
+ for (let i = 0; i < newSerialNumbers.length; i++) {
205
+ let newSerialNumber: string = newSerialNumbers[i]
206
+ let inventoryItemChange: any = {
207
+ serialNumber: newSerialNumber,
208
+ type: INVENTORY_ITEM_CHANGE_TYPE.NEW
209
+ }
210
+
211
+ inventoryItemChanges.push(inventoryItemChange)
212
+ }
213
+
214
+ newRecord.inventoryItemChangesJson = JSON.stringify(inventoryItemChanges)
215
+ }
216
+
217
+ // create new inventory items
218
+ if (newRecord?.inventoryItemChangesJson) {
219
+ const inventoryItemChanges: InventoryItemChange[] = JSON.parse(newRecord.inventoryItemChangesJson)
220
+ if (product?.isRequireSerialNumberScanningInbound) {
221
+ if (inventoryItemChanges?.length != newRecord?.qty) {
222
+ throw new Error('Serial Numbers not tally with quantity')
223
+ }
224
+ }
225
+
226
+ let newInventoryItemChanges: InventoryItemChange[] = []
227
+
228
+ inventoryItemChanges.map(inventoryItemChange => {
229
+ let newInventoryItemChange: InventoryItemChange = new InventoryItemChange()
230
+ newInventoryItemChange.name = InventoryNoGenerator.inventoryName()
231
+ newInventoryItemChange.serialNumber = inventoryItemChange.serialNumber
232
+ newInventoryItemChange.type = inventoryItemChange.type
233
+ newInventoryItemChange.status = newInventoryChange.status
234
+ newInventoryItemChange.inventoryItem =
235
+ newInventoryItemChange?.type == INVENTORY_ITEM_CHANGE_TYPE.NEW
236
+ ? null
237
+ : newInventoryItemChange.inventoryItem
238
+ newInventoryItemChange.inventoryChange = newInventoryChange
239
+ newInventoryItemChange.domain = domain
240
+ newInventoryItemChange.creator = user
241
+ newInventoryItemChange.updater = user
242
+
243
+ newInventoryItemChanges.push(newInventoryItemChange)
244
+ })
245
+
246
+ await inventoryItemChangeRepo.save(newInventoryItemChanges)
247
+ }
193
248
  }
194
249
  }
195
250
 
@@ -222,8 +277,14 @@ export class InventoryChangeMutation {
222
277
  if (!!updateRecord?.bizplace?.id)
223
278
  updateRecord.bizplace = await tx.getRepository(Bizplace).findOne(updateRecord.bizplace.id)
224
279
 
225
- if (!!updateRecord?.product?.id)
226
- updateRecord.product = await tx.getRepository(Product).findOne(updateRecord.product.id)
280
+ let product: Product
281
+
282
+ if (!!updateRecord?.product?.id) {
283
+ product = await tx.getRepository(Product).findOne(updateRecord.product.id)
284
+ updateRecord.product = product
285
+ } else {
286
+ product = existingRecord.product
287
+ }
227
288
 
228
289
  updateRecord.status = 'PENDING'
229
290
  updateRecord.transactionType = 'CHANGES'
@@ -233,7 +294,7 @@ export class InventoryChangeMutation {
233
294
  ? null
234
295
  : updateRecord.expirationDate
235
296
 
236
- await inventoryChangeRepo.save({
297
+ const updatedInventoryChange: InventoryChange = await inventoryChangeRepo.save({
237
298
  palletId: existingRecord.palletId,
238
299
  ...updateRecord,
239
300
  id: undefined,
@@ -243,6 +304,44 @@ export class InventoryChangeMutation {
243
304
  creator: user,
244
305
  updater: user
245
306
  })
307
+
308
+ // create new inventory items
309
+ if (updateRecord?.inventoryItemChangesJson) {
310
+ const originalInventoryItems: InventoryItem[] = await tx.getRepository(InventoryItem).find({
311
+ where: { domain, inventory: existingRecord }
312
+ })
313
+ const inventoryItemChanges: InventoryItemChange[] = JSON.parse(updateRecord.inventoryItemChangesJson)
314
+ if (product?.isRequireSerialNumberScanningInbound) {
315
+ const combinedInventoryItems: any[] = [...originalInventoryItems, ...inventoryItemChanges]
316
+ let qty: number = updateRecord?.qty ? updateRecord.qty : existingRecord.qty
317
+ if (combinedInventoryItems?.length != qty) {
318
+ throw new Error('Serial Numbers not tally with quantity')
319
+ }
320
+ }
321
+
322
+ let newInventoryItemChanges: InventoryItemChange[] = []
323
+
324
+ inventoryItemChanges.map(inventoryItemChange => {
325
+ let newInventoryItemChange: InventoryItemChange = new InventoryItemChange()
326
+ newInventoryItemChange.name = InventoryNoGenerator.inventoryName()
327
+ newInventoryItemChange.serialNumber = inventoryItemChange.serialNumber
328
+ newInventoryItemChange.type = inventoryItemChange.type
329
+ newInventoryItemChange.status = updatedInventoryChange.status
330
+ newInventoryItemChange.inventory = updatedInventoryChange.inventory
331
+ newInventoryItemChange.inventoryItem =
332
+ newInventoryItemChange?.type == INVENTORY_ITEM_CHANGE_TYPE.NEW
333
+ ? null
334
+ : newInventoryItemChange.inventoryItem
335
+ newInventoryItemChange.inventoryChange = updatedInventoryChange
336
+ newInventoryItemChange.domain = domain
337
+ newInventoryItemChange.creator = user
338
+ newInventoryItemChange.updater = user
339
+
340
+ newInventoryItemChanges.push(newInventoryItemChange)
341
+ })
342
+
343
+ await inventoryItemChangeRepo.save(newInventoryItemChanges)
344
+ }
246
345
  }
247
346
  }
248
347
 
@@ -509,6 +608,8 @@ export class InventoryChangeMutation {
509
608
  }
510
609
  }
511
610
 
611
+ await upsertInventoryItems(context, _inventoryChanges[i], 'APPROVED', tx)
612
+
512
613
  const sellercraft: Sellercraft = await tx
513
614
  .getRepository(Sellercraft)
514
615
  .findOne({ domain: customerDomain, status: SellercraftStatus.ACTIVE })
@@ -626,6 +727,8 @@ export class InventoryChangeMutation {
626
727
  _inventoryChanges[i].inventory = savedInventory
627
728
  _inventoryChanges[i].palletId = savedInventory.palletId
628
729
 
730
+ await upsertInventoryItems(context, _inventoryChanges[i], 'APPROVED', tx)
731
+
629
732
  const sellercraft: Sellercraft = await tx
630
733
  .getRepository(Sellercraft)
631
734
  .findOne({ domain: newRecord.bizplace.domain, status: SellercraftStatus.ACTIVE })
@@ -666,7 +769,7 @@ export class InventoryChangeMutation {
666
769
  @Arg('patches', type => [InventoryChangePatch]) patches: InventoryChangePatch[],
667
770
  @Ctx() context: any
668
771
  ): Promise<Boolean> {
669
- const { domain, tx }: { domain: Domain; tx: EntityManager } = context.state
772
+ const { domain, user, tx }: { domain: Domain; user: User; tx: EntityManager } = context.state
670
773
 
671
774
  const _inventoryChanges = await tx.getRepository(InventoryChange).find({
672
775
  where: { id: In(patches.map(item => item.id)) },
@@ -695,6 +798,8 @@ export class InventoryChangeMutation {
695
798
  })
696
799
 
697
800
  if (latestEntry.length > 0) _inventoryChanges[i].lastInventoryHistory = latestEntry[0]
801
+
802
+ await upsertInventoryItems(context, _inventoryChanges[i], 'REJECTED', tx)
698
803
  }
699
804
 
700
805
  await tx.getRepository(InventoryChange).save(_inventoryChanges.filter(change => change.status == 'REJECTED'))
@@ -712,3 +817,69 @@ function clean(obj) {
712
817
  }
713
818
  }
714
819
  }
820
+
821
+ async function upsertInventoryItems(context, inventoryChange, approvalStatus, tx) {
822
+ const { domain, user }: { domain: Domain; user: User } = context.state
823
+
824
+ let inventoryItemChanges: InventoryItemChange[] = await tx.getRepository(InventoryItemChange).find({
825
+ where: { domain, inventoryChange: inventoryChange, status: 'PENDING' },
826
+ relations: ['domain', 'inventoryItem', 'inventory', 'inventory.product', 'inventoryChange']
827
+ })
828
+ await Promise.all(
829
+ inventoryItemChanges.map(async inventoryItemChange => {
830
+ let inventoryItem: InventoryItem = new InventoryItem()
831
+ if (approvalStatus == 'APPROVED') {
832
+ const changeType: string = inventoryItemChange.type
833
+ const existingInventoryItem: InventoryItem = inventoryItemChange.inventoryItem
834
+ if (changeType == INVENTORY_ITEM_CHANGE_TYPE.NEW) {
835
+ const foundSerialNumber: Boolean = await checkExistingSerialNumber(
836
+ inventoryItemChange.serialNumber,
837
+ inventoryChange.inventory.product,
838
+ context,
839
+ inventoryChange.inventory,
840
+ tx
841
+ )
842
+
843
+ if (foundSerialNumber) throw new Error(`Serial number ${inventoryItemChange.serialNumber} is duplicated`)
844
+
845
+ inventoryItem.name = InventoryNoGenerator.inventoryName()
846
+ inventoryItem.serialNumber = inventoryItemChange.serialNumber
847
+ inventoryItem.status = INVENTORY_STATUS.STORED
848
+ inventoryItem.product = inventoryChange.inventory.product
849
+ inventoryItem.domain = inventoryItemChange.domain
850
+ inventoryItem.inventory = inventoryChange.inventory
851
+ inventoryItem.source = INVENTORY_ITEM_SOURCE.ADJUSTMENT
852
+ inventoryItem.creator = user
853
+ inventoryItem.updater = user
854
+ } else if (changeType == INVENTORY_ITEM_CHANGE_TYPE.MODIFIED) {
855
+ const foundSerialNumber: Boolean = await checkExistingSerialNumber(
856
+ inventoryItemChange.serialNumber,
857
+ inventoryItemChange.inventory.product,
858
+ context,
859
+ inventoryItemChange.inventory,
860
+ tx
861
+ )
862
+
863
+ if (foundSerialNumber) throw new Error(`Serial number ${inventoryItemChange.serialNumber} is duplicated`)
864
+
865
+ inventoryItem = {
866
+ ...existingInventoryItem,
867
+ serialNumber: inventoryItemChange.serialNumber,
868
+ product: inventoryItemChange.inventory.product
869
+ }
870
+ } else if (changeType == INVENTORY_ITEM_CHANGE_TYPE.REMOVED) {
871
+ inventoryItem = {
872
+ ...existingInventoryItem,
873
+ status: INVENTORY_STATUS.DELETED
874
+ }
875
+ }
876
+
877
+ inventoryItem = await tx.getRepository(InventoryItem).save(inventoryItem)
878
+ }
879
+
880
+ await tx
881
+ .getRepository(InventoryItemChange)
882
+ .update({ id: inventoryItemChange.id }, { status: approvalStatus, inventoryItem })
883
+ })
884
+ )
885
+ }
@@ -44,6 +44,7 @@ export class InventoryChangeQuery {
44
44
  .leftJoinAndSelect('ic.product', 'product')
45
45
  .leftJoinAndSelect('ic.location', 'location')
46
46
  .leftJoinAndSelect('ic.lastInventoryHistory', 'livh')
47
+ .leftJoinAndSelect('ic.inventoryItemChange', 'itc')
47
48
  .leftJoinAndSelect('livh.product', 'livhProduct')
48
49
  .leftJoinAndSelect('livh.warehouse', 'livhWarehouse')
49
50
  .leftJoinAndSelect('livh.location', 'livhLocation')
@@ -16,6 +16,7 @@ import { Product, ProductDetail } from '@things-factory/product-base'
16
16
  import { Domain, ScalarDate } from '@things-factory/shell'
17
17
 
18
18
  import { InventoryHistory } from '../inventory-history/inventory-history'
19
+ import { InventoryItemChange } from '../inventory-item-change/inventory-item-change'
19
20
  import { Inventory } from '../inventory/inventory'
20
21
  import { Location } from '../location/location'
21
22
 
@@ -72,6 +73,10 @@ export class InventoryChange {
72
73
  @Field(type => Product, { nullable: true })
73
74
  product: Product
74
75
 
76
+ @ManyToOne(type => InventoryItemChange, { nullable: true })
77
+ @Field(type => InventoryItemChange, { nullable: true })
78
+ inventoryItemChange: InventoryItemChange
79
+
75
80
  /**
76
81
  * To be updated to remove nullable. once all process that links to inventory has been updated to use product detail instead of product
77
82
  */
@@ -1,7 +1,8 @@
1
- import { Arg, Args, Ctx, FieldResolver, Query, Resolver, Root } from 'type-graphql'
2
- import { Brackets, getRepository, SelectQueryBuilder } from 'typeorm'
1
+ import { Arg, Args, Ctx, Directive, FieldResolver, Query, Resolver, Root } from 'type-graphql'
2
+ import { Brackets, EntityManager, getRepository, In, Not, SelectQueryBuilder } from 'typeorm'
3
3
 
4
4
  import { User } from '@things-factory/auth-base'
5
+ import { Product } from '@things-factory/product-base'
5
6
  import { convertListParams, Domain, ListParam } from '@things-factory/shell'
6
7
 
7
8
  import { INVENTORY_STATUS } from '../../constants'
@@ -25,11 +26,81 @@ export class InventoryItemQuery {
25
26
  const { domain } = context.state
26
27
 
27
28
  const convertedParams = convertListParams(params, domain.id)
28
- const [items, total] = await getRepository(InventoryItem).findAndCount(convertedParams)
29
+ const [items, total] = await getRepository(InventoryItem).findAndCount({
30
+ ...convertedParams,
31
+ relations: ['inventory', 'product']
32
+ })
29
33
 
30
34
  return { items, total }
31
35
  }
32
36
 
37
+ @Directive('@transaction')
38
+ @Query(returns => InventoryItemList, { description: 'To fetch multiple InventoryItems with InventoryItemChanges' })
39
+ async inventoryItemsWithChanges(@Args() params: ListParam, @Ctx() context: any): Promise<InventoryItemList> {
40
+ const { domain, tx }: { domain: Domain; tx: EntityManager } = context.state
41
+
42
+ const page = params?.pagination?.page
43
+ const limit = params?.pagination?.limit
44
+
45
+ let inventoryFilter = params.filters.find(filter => filter.name == 'inventoryId')
46
+
47
+ let items: any = []
48
+ let total: any = 0
49
+ if (inventoryFilter) {
50
+ await tx.query(
51
+ `
52
+ create temp table temp_inventory_items on commit drop as (
53
+ select * from (
54
+ select ii.id, ii.name, ii.serial_number, '' as "type", 'ORIGINAL' as "transaction_type", i2.id as inventory_id, i2.name as inventory_name, i2.pallet_id as pallet_id, i2.carton_id as carton_id, p2.id as product_id, p2.name as product_name, p2.sku as product_sku from inventory_items ii
55
+ inner join inventories i2 on i2.id = ii.inventory_id
56
+ inner join products p2 on p2.id = i2.product_id
57
+ where ii.domain_id = $1 and ii.inventory_id = $2 and ii.status = $3
58
+ ) src
59
+ )
60
+ `,
61
+ [domain.id, inventoryFilter.value, INVENTORY_STATUS.STORED]
62
+ )
63
+
64
+ const offsetQuery: string = params?.pagination ? `OFFSET ${(page - 1) * limit} LIMIT ${limit}` : ''
65
+
66
+ items = await tx.query(`
67
+ select * from temp_inventory_items ${offsetQuery}
68
+ `)
69
+
70
+ total = await tx.query(`
71
+ select count(*) from temp_inventory_items
72
+ `)
73
+
74
+ items = items.map(item => {
75
+ return {
76
+ id: item.id,
77
+ name: item.name,
78
+ serialNumber: item.serial_number,
79
+ type: item.type,
80
+ transactionType: item.transaction_type,
81
+ inventory: {
82
+ id: item.inventory_id,
83
+ name: item.inventory_name,
84
+ palletId: item.pallet_id,
85
+ cartonId: item.carton_id
86
+ },
87
+ product: {
88
+ id: item.product_id,
89
+ name: item.product_name,
90
+ sku: item.product_sku
91
+ }
92
+ }
93
+ })
94
+
95
+ total = total[0].count
96
+ }
97
+
98
+ return {
99
+ items,
100
+ total
101
+ }
102
+ }
103
+
33
104
  @Query(returns => InventoryItemList, { description: 'To fetch multiple InventoryItems by inventory' })
34
105
  async inventoryItemsByInventory(@Args() params: ListParam, @Ctx() context: any): Promise<InventoryItemList> {
35
106
  const { domain } = context.state
@@ -47,6 +118,7 @@ export class InventoryItemQuery {
47
118
  qb.leftJoinAndSelect('ivi.product', 'product').leftJoinAndSelect('ivi.inventory', 'inventory')
48
119
  qb.where('ivi.product_id=:productId', { productId: productIdFilter.value })
49
120
  qb.andWhere('inventory.status!=:inventoryStatus', { inventoryStatus: INVENTORY_STATUS.PARTIALLY_UNLOADED })
121
+ qb.andWhere('inventory.domain_id=:domainId', { domainId: domain.id })
50
122
 
51
123
  if (orderTypeFilter.value == 'inbound') {
52
124
  qb.andWhere('ivi.inbound_order_id=:orderId', { orderId: orderIdFilter.value })
@@ -116,3 +188,26 @@ export class InventoryItemQuery {
116
188
  return await getRepository(User).findOne(inventoryItem.creatorId)
117
189
  }
118
190
  }
191
+
192
+ export async function checkExistingSerialNumber(
193
+ serialNumber: string,
194
+ product: Product,
195
+ context: any,
196
+ inventory?: Inventory,
197
+ tx?: EntityManager
198
+ ) {
199
+ const { domain }: { domain: Domain } = context.state
200
+
201
+ let countCondition: any = {
202
+ domain,
203
+ product,
204
+ serialNumber,
205
+ status: Not(In([INVENTORY_STATUS.DELETED, INVENTORY_STATUS.TERMINATED]))
206
+ }
207
+
208
+ if (inventory) countCondition = { ...countCondition, inventory }
209
+
210
+ const duplicatedSerialNumberCnt: number = await tx.getRepository(InventoryItem).count(countCondition)
211
+
212
+ if (duplicatedSerialNumberCnt) return true
213
+ }