@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.
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/server/constants/index.ts +0 -5
- package/server/constants/inventory.ts +0 -67
- package/server/constants/location.ts +0 -14
- package/server/constants/pallet.ts +0 -10
- package/server/constants/rule-type.ts +0 -5
- package/server/constants/tote.ts +0 -5
- package/server/controllers/ecommerce/ecommerce-controller.ts +0 -108
- package/server/controllers/ecommerce/index.ts +0 -2
- package/server/controllers/ecommerce/sellercraft-controller.ts +0 -100
- package/server/controllers/index.ts +0 -2
- package/server/controllers/warehouse-controller.ts +0 -181
- package/server/index.ts +0 -9
- package/server/middlewares/index.ts +0 -0
- package/server/migrations/index.ts +0 -9
- package/server/service/index.ts +0 -80
- package/server/service/inventory/index.ts +0 -6
- package/server/service/inventory/inventory-mutation.ts +0 -530
- package/server/service/inventory/inventory-query.ts +0 -1263
- package/server/service/inventory/inventory-types.ts +0 -367
- package/server/service/inventory/inventory.ts +0 -408
- package/server/service/inventory-change/index.ts +0 -6
- package/server/service/inventory-change/inventory-change-mutation.ts +0 -969
- package/server/service/inventory-change/inventory-change-query.ts +0 -93
- package/server/service/inventory-change/inventory-change-types.ts +0 -36
- package/server/service/inventory-change/inventory-change.ts +0 -164
- package/server/service/inventory-history/index.ts +0 -6
- package/server/service/inventory-history/inventory-history-mutation.ts +0 -116
- package/server/service/inventory-history/inventory-history-query.ts +0 -1845
- package/server/service/inventory-history/inventory-history-types.ts +0 -444
- package/server/service/inventory-history/inventory-history.ts +0 -203
- package/server/service/inventory-item/index.ts +0 -6
- package/server/service/inventory-item/inventory-item-mutation.ts +0 -217
- package/server/service/inventory-item/inventory-item-query.ts +0 -226
- package/server/service/inventory-item/inventory-item-type.ts +0 -74
- package/server/service/inventory-item/inventory-item.ts +0 -105
- package/server/service/inventory-item-change/index.ts +0 -6
- package/server/service/inventory-item-change/inventory-item-change-mutation.ts +0 -119
- package/server/service/inventory-item-change/inventory-item-change-query.ts +0 -47
- package/server/service/inventory-item-change/inventory-item-change-type.ts +0 -68
- package/server/service/inventory-item-change/inventory-item-change.ts +0 -92
- package/server/service/inventory-product/index.ts +0 -6
- package/server/service/inventory-product/inventory-product-mutation.ts +0 -116
- package/server/service/inventory-product/inventory-product-query.ts +0 -47
- package/server/service/inventory-product/inventory-product-type.ts +0 -59
- package/server/service/inventory-product/inventory-product.ts +0 -88
- package/server/service/location/index.ts +0 -6
- package/server/service/location/location-mutation.ts +0 -134
- package/server/service/location/location-query.ts +0 -244
- package/server/service/location/location-types.ts +0 -173
- package/server/service/location/location.ts +0 -121
- package/server/service/movement/index.ts +0 -6
- package/server/service/movement/movement-mutation.ts +0 -60
- package/server/service/movement/movement-query.ts +0 -263
- package/server/service/movement/movement-types.ts +0 -74
- package/server/service/movement/movement.ts +0 -81
- package/server/service/pallet/index.ts +0 -6
- package/server/service/pallet/pallet-mutation.ts +0 -242
- package/server/service/pallet/pallet-query.ts +0 -106
- package/server/service/pallet/pallet-types.ts +0 -80
- package/server/service/pallet/pallet.ts +0 -92
- package/server/service/pallet-count/index.ts +0 -6
- package/server/service/pallet-count/pallet-count-mutation.ts +0 -151
- package/server/service/pallet-count/pallet-count-query.ts +0 -45
- package/server/service/pallet-count/pallet-count-types.ts +0 -36
- package/server/service/pallet-count/pallet-count.ts +0 -70
- package/server/service/pallet-history/index.ts +0 -6
- package/server/service/pallet-history/pallet-history-mutation.ts +0 -114
- package/server/service/pallet-history/pallet-history-query.ts +0 -48
- package/server/service/pallet-history/pallet-history-types.ts +0 -36
- package/server/service/pallet-history/pallet-history.ts +0 -89
- package/server/service/reduced-inventory-history/index.ts +0 -3
- package/server/service/reduced-inventory-history/reduced-inventory-history.ts +0 -92
- package/server/service/tote/index.ts +0 -6
- package/server/service/tote/tote-mutation.ts +0 -201
- package/server/service/tote/tote-query.ts +0 -106
- package/server/service/tote/tote-types.ts +0 -44
- package/server/service/tote/tote.ts +0 -77
- package/server/service/warehouse/index.ts +0 -6
- package/server/service/warehouse/warehouse-mutation.ts +0 -152
- package/server/service/warehouse/warehouse-query.ts +0 -58
- package/server/service/warehouse/warehouse-types.ts +0 -50
- package/server/service/warehouse/warehouse.ts +0 -95
- package/server/utils/datetime-util.ts +0 -54
- package/server/utils/index.ts +0 -3
- package/server/utils/inventory-no-generator.ts +0 -15
- 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
|
-
}
|