@things-factory/product-base 8.0.0-beta.9 → 8.0.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 (63) hide show
  1. package/dist-server/service/product/product-mutation.js +1 -1
  2. package/dist-server/service/product/product-mutation.js.map +1 -1
  3. package/dist-server/service/product/product-query.d.ts +1 -1
  4. package/dist-server/service/product/product-query.js +5 -5
  5. package/dist-server/service/product/product-query.js.map +1 -1
  6. package/dist-server/service/product/product-types.d.ts +4 -2
  7. package/dist-server/service/product/product-types.js +10 -2
  8. package/dist-server/service/product/product-types.js.map +1 -1
  9. package/dist-server/service/product/product.d.ts +2 -1
  10. package/dist-server/service/product/product.js +6 -1
  11. package/dist-server/service/product/product.js.map +1 -1
  12. package/dist-server/tsconfig.tsbuildinfo +1 -1
  13. package/package.json +5 -5
  14. package/server/constants/index.ts +1 -0
  15. package/server/constants/product.ts +24 -0
  16. package/server/controllers/index.ts +0 -0
  17. package/server/index.ts +2 -0
  18. package/server/middlewares/index.ts +0 -0
  19. package/server/migrations/index.ts +9 -0
  20. package/server/service/index.ts +61 -0
  21. package/server/service/product/index.ts +6 -0
  22. package/server/service/product/product-mutation.ts +407 -0
  23. package/server/service/product/product-query.ts +336 -0
  24. package/server/service/product/product-types.ts +458 -0
  25. package/server/service/product/product.ts +548 -0
  26. package/server/service/product/validate-product.ts +42 -0
  27. package/server/service/product-bundle/index.ts +6 -0
  28. package/server/service/product-bundle/product-bundle-mutation.ts +140 -0
  29. package/server/service/product-bundle/product-bundle-query.ts +104 -0
  30. package/server/service/product-bundle/product-bundle-types.ts +51 -0
  31. package/server/service/product-bundle/product-bundle.ts +102 -0
  32. package/server/service/product-bundle-setting/index.ts +6 -0
  33. package/server/service/product-bundle-setting/product-bundle-setting-mutation.ts +168 -0
  34. package/server/service/product-bundle-setting/product-bundle-setting-query.ts +139 -0
  35. package/server/service/product-bundle-setting/product-bundle-setting-types.ts +41 -0
  36. package/server/service/product-bundle-setting/product-bundle-setting.ts +45 -0
  37. package/server/service/product-combination/index.ts +6 -0
  38. package/server/service/product-combination/product-combination-mutation.ts +148 -0
  39. package/server/service/product-combination/product-combination-query.ts +48 -0
  40. package/server/service/product-combination/product-combination-type.ts +50 -0
  41. package/server/service/product-combination/product-combination.ts +116 -0
  42. package/server/service/product-combination-setting/index.ts +6 -0
  43. package/server/service/product-combination-setting/product-combination-setting-mutation.ts +248 -0
  44. package/server/service/product-combination-setting/product-combination-setting-query.ts +176 -0
  45. package/server/service/product-combination-setting/product-combination-setting-type.ts +44 -0
  46. package/server/service/product-combination-setting/product-combination-setting.ts +77 -0
  47. package/server/service/product-detail/index.ts +6 -0
  48. package/server/service/product-detail/product-detail-mutation.ts +238 -0
  49. package/server/service/product-detail/product-detail-query.ts +213 -0
  50. package/server/service/product-detail/product-detail-types.ts +280 -0
  51. package/server/service/product-detail/product-detail.ts +388 -0
  52. package/server/service/product-detail-bizplace-setting/index.ts +6 -0
  53. package/server/service/product-detail-bizplace-setting/product-detail-bizplace-setting-mutation.ts +118 -0
  54. package/server/service/product-detail-bizplace-setting/product-detail-bizplace-setting-query.ts +90 -0
  55. package/server/service/product-detail-bizplace-setting/product-detail-bizplace-setting-types.ts +62 -0
  56. package/server/service/product-detail-bizplace-setting/product-detail-bizplace-setting.ts +104 -0
  57. package/server/service/product-set/index.ts +6 -0
  58. package/server/service/product-set/product-set-mutation.ts +149 -0
  59. package/server/service/product-set/product-set-query.ts +114 -0
  60. package/server/service/product-set/product-set-types.ts +45 -0
  61. package/server/service/product-set/product-set.ts +95 -0
  62. package/server/utils/index.ts +1 -0
  63. package/server/utils/product-util.ts +11 -0
@@ -0,0 +1,238 @@
1
+ import _ from 'lodash'
2
+ import { Arg, Ctx, Directive, Mutation, Resolver } from 'type-graphql'
3
+ import { In, Raw } from 'typeorm'
4
+
5
+ import { getRepository } from '@things-factory/shell'
6
+
7
+ import { Product } from '../product/product'
8
+ import { ProductDetail } from './product-detail'
9
+ import { ProductDetailPatch } from './product-detail-types'
10
+
11
+ @Resolver(ProductDetail)
12
+ export class ProductDetailMutation {
13
+ @Directive('@transaction')
14
+ @Mutation(returns => ProductDetail)
15
+ async updateProductDetail(
16
+ @Arg('id') id: string,
17
+ @Arg('patch') patch: ProductDetailPatch,
18
+ @Ctx() context: ResolverContext
19
+ ): Promise<ProductDetail> {
20
+ return await updateProductDetail(id, patch, context)
21
+ }
22
+
23
+ @Directive('@transaction')
24
+ @Mutation(returns => [ProductDetail])
25
+ async updateMultipleProductDetail(
26
+ @Arg('productId') productId: string,
27
+ @Arg('patches', type => [ProductDetailPatch]) patches: ProductDetailPatch[],
28
+ @Ctx() context: ResolverContext
29
+ ): Promise<ProductDetail[]> {
30
+ try {
31
+ return await updateMultipleProductDetail(productId, patches, context)
32
+ } catch (error) {
33
+ throw error
34
+ }
35
+ }
36
+
37
+ @Directive('@transaction')
38
+ @Mutation(returns => Boolean)
39
+ async deleteProductDetail(@Arg('name') name: string, @Ctx() context: ResolverContext): Promise<Boolean> {
40
+ const { domain, user } = context.state
41
+
42
+ await getRepository(ProductDetail).delete({ domain: { id: domain.id }, name })
43
+
44
+ return true
45
+ }
46
+
47
+ @Directive('@transaction')
48
+ @Mutation(returns => Boolean)
49
+ async deleteProductDetails(
50
+ @Arg('names', type => [String]) names: string[],
51
+ @Ctx() context: ResolverContext
52
+ ): Promise<Boolean> {
53
+ const { domain, user } = context.state
54
+
55
+ await getRepository(ProductDetail).delete({
56
+ domain: { id: domain.id },
57
+ name: In(names)
58
+ })
59
+
60
+ return true
61
+ }
62
+ }
63
+
64
+ export async function createProductDetail(productDetail: any, context: ResolverContext) {
65
+ const { domain, user, tx } = context.state
66
+
67
+ return await tx.getRepository(ProductDetail).save({
68
+ ...productDetail,
69
+ domain,
70
+ creator: user,
71
+ updater: user
72
+ })
73
+ }
74
+
75
+ export async function updateProductDetail(id: string, patch: any, context: ResolverContext) {
76
+ const { domain, user, tx } = context.state
77
+
78
+ const repository = tx.getRepository(ProductDetail)
79
+ const productDetail: ProductDetail = await repository.findOne({ where: { id } })
80
+
81
+ if (patch.gtin == undefined) {
82
+ delete patch.gtin
83
+ }
84
+
85
+ return await repository.save({
86
+ ...productDetail,
87
+ ...patch,
88
+ updater: user
89
+ })
90
+ }
91
+
92
+ export async function updateMultipleProductDetail(productId, patches, context: ResolverContext) {
93
+ try {
94
+ const { domain, user, tx } = context.state
95
+
96
+ let results = []
97
+ const _createRecords = patches.filter((patch: any) => !patch.id)
98
+ const _updateRecords = patches.filter((patch: any) => patch.id)
99
+ // const _updateRecords = patches.filter((patch: any) => patch.cuFlag.toUpperCase() === 'M')
100
+
101
+ const productRepo = tx.getRepository(Product)
102
+ const productDetailRepo = tx.getRepository(ProductDetail)
103
+
104
+ const product: Product = await productRepo.findOne({ where: { id: productId }, relations: ['productDetails'] })
105
+
106
+ const defaultProductDetail: ProductDetail = patches.find(x => x.isDefault)
107
+
108
+ if (_createRecords.length > 0) {
109
+ for (let i = 0; i < _createRecords.length; i++) {
110
+ const newRecord = {
111
+ ..._createRecords[i],
112
+ name: _createRecords[i].gtin,
113
+ product,
114
+ childProductDetail: null,
115
+ childQty: 0,
116
+ domain: domain,
117
+ creator: user,
118
+ updater: user
119
+ }
120
+
121
+ if (newRecord.gtin == null || newRecord.gtin == undefined) {
122
+ throw new Error('GTIN cannot be empty.')
123
+ }
124
+
125
+ const result = await productDetailRepo.save(newRecord)
126
+
127
+ results.push({ ...result, cuFlag: '+' })
128
+ }
129
+ }
130
+
131
+ let deleteIds = []
132
+ for (let x = 0; x < product.productDetails.length; x++) {
133
+ let updateRecords = _updateRecords.find(ur => ur.id == product.productDetails[x].id)
134
+
135
+ if (updateRecords) {
136
+ const productDetail: ProductDetail = await productDetailRepo.findOneBy({ id: updateRecords.id })
137
+
138
+ const updateRecord = {
139
+ ...productDetail,
140
+ ...updateRecords,
141
+ name: updateRecords.gtin,
142
+ product,
143
+ childProductDetail: null,
144
+ childQty: 0,
145
+ updater: user
146
+ }
147
+
148
+ if (updateRecord.gtin == '' || updateRecord.gtin == null || updateRecord.gtin == undefined) {
149
+ throw new Error('GTIN cannot be empty.')
150
+ }
151
+
152
+ const result = await productDetailRepo.save({
153
+ ...productDetail,
154
+ ...updateRecord,
155
+ name: updateRecord.gtin,
156
+ product,
157
+ childProductDetail: null,
158
+ childQty: 0,
159
+ updater: user
160
+ })
161
+
162
+ results.push({ ...result, cuFlag: 'M' })
163
+ } else {
164
+ deleteIds.push(product.productDetails[x].id)
165
+ }
166
+ }
167
+
168
+ await productDetailRepo.delete({ id: In(deleteIds) })
169
+
170
+ for await (let patch of patches.filter(x => x.childProductDetail)) {
171
+ let matchedChildProductDetail = results.find(itm => itm.gtin == patch.childProductDetail)
172
+
173
+ if (matchedChildProductDetail) {
174
+ const childProductDetail: ProductDetail = await productDetailRepo.findOne({
175
+ where: { id: matchedChildProductDetail.id },
176
+ relations: ['product']
177
+ })
178
+ const productDetail: ProductDetail = await productDetailRepo.findOne({
179
+ where: { gtin: patch.gtin, product: { id: childProductDetail.product.id } }
180
+ })
181
+
182
+ await productDetailRepo.save({
183
+ ...productDetail,
184
+ childQty: patch.childQty,
185
+ childProductDetail: childProductDetail,
186
+ updater: user
187
+ })
188
+ }
189
+ }
190
+
191
+ let defaultProductUpdate: Product
192
+
193
+ //// Temporary solution, to fetch the first case packing type of SKU and update Case data in Product Table.
194
+ //// To be removed
195
+ let firstCase: ProductDetail = await productDetailRepo.findOne({
196
+ where: { product: { id: product.id }, packingType: Raw(alias => `lower(${alias}) = 'case'`) },
197
+ order: {
198
+ name: 'ASC'
199
+ }
200
+ })
201
+ if (firstCase) {
202
+ defaultProductUpdate = {
203
+ ...defaultProductUpdate,
204
+ caseGtin: firstCase.gtin,
205
+ caseWidth: firstCase.width,
206
+ caseHeight: firstCase.height,
207
+ caseDepth: firstCase.depth,
208
+ caseGrossWeight: firstCase.grossWeight,
209
+ caseWeight: firstCase.nettWeight,
210
+ caseVolume: firstCase.volume
211
+ }
212
+ }
213
+ /////////////////////////////////////////////////////
214
+
215
+ if (defaultProductDetail) {
216
+ defaultProductUpdate = {
217
+ ...defaultProductUpdate,
218
+ packingType: defaultProductDetail.packingType,
219
+ primaryUnit: defaultProductDetail.uom,
220
+ primaryValue: defaultProductDetail.uomValue,
221
+ cogsAccountCode: defaultProductDetail.cogsAccountCode
222
+ }
223
+ }
224
+
225
+ if (!_.isEmpty(defaultProductUpdate)) {
226
+ await productRepo.update(
227
+ {
228
+ id: product.id
229
+ },
230
+ defaultProductUpdate
231
+ )
232
+ }
233
+
234
+ return results
235
+ } catch (error) {
236
+ throw error
237
+ }
238
+ }
@@ -0,0 +1,213 @@
1
+ import { Arg, Ctx, Directive, FieldResolver, Query, Resolver, Root } from 'type-graphql'
2
+ import { Brackets, SelectQueryBuilder } from 'typeorm'
3
+
4
+ import { User } from '@things-factory/auth-base'
5
+ import { Bizplace, getCompaniesBizplaces, getPartnersCompanyBizplaces } from '@things-factory/biz-base'
6
+ import { buildCondition, buildQuery, Domain, Filter, getRepository, Pagination, Sorting } from '@things-factory/shell'
7
+
8
+ import { ProductDetail } from './product-detail'
9
+ import { ProductDetailList } from './product-detail-types'
10
+
11
+ @Resolver(ProductDetail)
12
+ export class ProductDetailQuery {
13
+ @Directive('@transaction')
14
+ @Query(returns => ProductDetail)
15
+ async productDetail(
16
+ @Arg('gtin') gtin: string,
17
+ @Arg('productId') productId: string,
18
+ @Arg('customerCompanyDomainId') customerCompanyDomainId: string,
19
+ @Ctx() context: ResolverContext
20
+ ): Promise<ProductDetail> {
21
+ const { tx } = context.state
22
+
23
+ const customerDomain = await tx.getRepository(Domain).findOneBy({ id: customerCompanyDomainId })
24
+ if (!customerDomain) {
25
+ throw new Error('Unable to find company domain')
26
+ }
27
+
28
+ var foundProductDetail
29
+ if (productId) {
30
+ foundProductDetail = await tx.getRepository(ProductDetail).findOne({
31
+ where: { domain: { id: customerDomain.id }, product: { id: productId } },
32
+ relations: ['product']
33
+ })
34
+ } else {
35
+ foundProductDetail = await tx.getRepository(ProductDetail).findOne({
36
+ where: { domain: { id: customerDomain.id }, gtin },
37
+ relations: ['product']
38
+ })
39
+ }
40
+
41
+ if (!foundProductDetail) throw new Error('No product candidate found')
42
+
43
+ return foundProductDetail
44
+ }
45
+
46
+ @Query(returns => ProductDetailList)
47
+ async productDetails(
48
+ @Ctx() context: ResolverContext,
49
+ @Arg('filters', type => [Filter], { nullable: true }) filters?: Filter[],
50
+ @Arg('pagination', type => Pagination, { nullable: true }) pagination?: Pagination,
51
+ @Arg('sortings', type => [Sorting], { nullable: true }) sortings?: Sorting[]
52
+ ): Promise<ProductDetailList> {
53
+ const { domain, user } = context.state
54
+ const { domains } = user
55
+
56
+ const params = { filters, pagination }
57
+ const productInfoFilters = params.filters.find(x => x.name == 'product_info' || x.name == 'sku' || x.name == 'name' || x.name == 'description' || x.name == 'type')
58
+
59
+ const bizplaceFilter = params.filters.find(x => x.name == 'bizplace_id')
60
+ const productFilter = params.filters.find(x => x.name == 'product')
61
+ const createArrivalNotice = params.filters.find(x => x.name == 'create_arrival_notice')
62
+ const productInfoFilterColumns = ['sku', 'name', 'description', 'type', 'brand']
63
+ params.filters = params.filters.filter(
64
+ x =>
65
+ x.name != 'product_info' &&
66
+ x.name != 'bizplace_id' &&
67
+ x.name != 'product' &&
68
+ x.name != 'sku' &&
69
+ x.name != 'brand' &&
70
+ x.name != 'name' &&
71
+ x.name != 'description' &&
72
+ x.name != 'type' &&
73
+ x.name != 'create_arrival_notice'
74
+ )
75
+
76
+ const qb: SelectQueryBuilder<ProductDetail> = getRepository(ProductDetail).createQueryBuilder('ProductDetail')
77
+ qb.innerJoinAndSelect('ProductDetail.product', 'Product', '"Product"."deleted_at" IS NULL')
78
+ qb.innerJoinAndSelect('Product.bizplace', 'Bizplace')
79
+ qb.leftJoinAndSelect('ProductDetail.childProductDetail', 'ChildProductDetail')
80
+ qb.leftJoinAndSelect('Product.creator', 'Creator')
81
+ qb.leftJoinAndSelect('Product.updater', 'Updater')
82
+
83
+ buildQuery(qb, params, context, {
84
+ domainRef: false,
85
+ searchables: ['name']
86
+ })
87
+
88
+ if (bizplaceFilter?.value) {
89
+ const bizplaceId: string = bizplaceFilter.value
90
+
91
+ const foundBizplace: Bizplace = await getRepository(Bizplace).findOne({
92
+ where: { id: bizplaceId } /* CONFIRMME regarding TYPEORM - did I migrate correctly ? */,
93
+ relations: ['company', 'company.domain']
94
+ })
95
+
96
+ if (foundBizplace) {
97
+ const companyDomain: Domain = foundBizplace.company?.domain
98
+ const companyBizplace: Bizplace = await getRepository(Bizplace).findOne({
99
+ where: { domain: { id: companyDomain.id } }
100
+ })
101
+
102
+ const bizplaceIds = [companyBizplace.id, foundBizplace.id]
103
+ qb.andWhere('Bizplace.id IN (:...bizplaceIds)', { bizplaceIds: [...new Set(bizplaceIds)] })
104
+ }
105
+ } else {
106
+ const warehouseDomain: Domain = domain
107
+ let bizplaceIds = []
108
+ if (warehouseDomain?.extType === 'company') {
109
+ const companyBizplace: Bizplace = await getRepository(Bizplace).findOne({
110
+ where: { domain: { id: warehouseDomain.id } }
111
+ })
112
+ bizplaceIds.push(companyBizplace.id)
113
+ } else if (warehouseDomain?.extType === 'factory') {
114
+ const myBizplace: Bizplace = await getRepository(Bizplace).findOne({
115
+ where: { domain: { id: warehouseDomain.id } },
116
+ relations: ['domain']
117
+ })
118
+ const companiesBizplaces: Bizplace[] = await getCompaniesBizplaces(domains, warehouseDomain)
119
+ const partnersCompanyBizplaces: Bizplace[] = await getPartnersCompanyBizplaces(domain, user)
120
+ bizplaceIds = [...bizplaceIds, myBizplace.id, ...companiesBizplaces.map(biz => biz.id), ...partnersCompanyBizplaces.map(biz => biz.id)]
121
+ } else {
122
+ const companiesBizplaces: Bizplace[] = await getCompaniesBizplaces(domains, warehouseDomain)
123
+ const partnersCompanyBizplaces: Bizplace[] = await getPartnersCompanyBizplaces(domain, user)
124
+ bizplaceIds = [...bizplaceIds, ...companiesBizplaces.map(biz => biz.id), ...partnersCompanyBizplaces.map(biz => biz.id)]
125
+ }
126
+
127
+ qb.andWhere('Product.bizplace_id IN (:...bizplaceIds)', { bizplaceIds: bizplaceIds })
128
+ }
129
+
130
+ if (productFilter?.value) {
131
+ if (productFilter.operator === 'eq') {
132
+ qb.andWhere('ProductDetail.product_id = :productId', { productId: productFilter.value })
133
+ } else if (productFilter.operator === 'in') {
134
+ qb.andWhere('ProductDetail.product_id in(:...productId)', { productId: productFilter.value })
135
+ }
136
+ }
137
+
138
+ if (productInfoFilters?.value) {
139
+ let productInfo = productInfoFilters
140
+ qb.andWhere(
141
+ new Brackets(qb2 => {
142
+ productInfoFilterColumns.forEach(filter => {
143
+ const condition = buildCondition('Product', filter, productInfo.operator, productInfo.value, productInfo.relation, Object.keys(qb.getParameters()).length)
144
+
145
+ qb2.orWhere(condition.clause, condition.parameters)
146
+ })
147
+ })
148
+ )
149
+ }
150
+
151
+ if (createArrivalNotice?.value) {
152
+ qb.andWhere('(Product.isRequireSerialNumberScanning = false OR (Product.isRequireSerialNumberScanning = true AND ProductDetail.isDefault = true))')
153
+ }
154
+
155
+ let [items, total] = await qb.getManyAndCount()
156
+
157
+ items = items.map(item => {
158
+ return {
159
+ ...item,
160
+ productId: item.product.id,
161
+ name: item.product.name,
162
+ description: item.product.description,
163
+ type: item.product.type,
164
+ sku: item.product.sku,
165
+ isRequireSerialNumberScanningInbound: item.product.isRequireSerialNumberScanningInbound,
166
+ isRequireSerialNumberScanningOutbound: item.product.isRequireSerialNumberScanningOutbound
167
+ }
168
+ })
169
+
170
+ return { items, total }
171
+ }
172
+
173
+ @Query(returns => ProductDetailList)
174
+ async checkGtin(
175
+ @Arg('gtin') gtin: string,
176
+ @Arg('customerCompanyDomainId') customerCompanyDomainId: string,
177
+ @Ctx() context: ResolverContext
178
+ ): Promise<ProductDetailList> {
179
+ try {
180
+ const { tx } = context.state
181
+
182
+ const customerDomainId: Domain = await getRepository(Domain).findOneBy({ id: customerCompanyDomainId })
183
+ if (!customerDomainId) throw new Error('Unable to find company domain')
184
+ var foundProductDetails
185
+
186
+ let [items, total] = await getRepository(ProductDetail).findAndCount({
187
+ where: { domain: { id: customerDomainId.id }, gtin },
188
+ relations: ['product']
189
+ })
190
+
191
+ if (items?.length <= 0) throw new Error('No product candidate found')
192
+
193
+ return { items, total }
194
+ } catch (e) {
195
+ throw new Error(e)
196
+ }
197
+ }
198
+
199
+ @FieldResolver(type => Domain)
200
+ async domain(@Root() productDetail: ProductDetail): Promise<Domain> {
201
+ return await getRepository(Domain).findOneBy({ id: productDetail.domain.id })
202
+ }
203
+
204
+ @FieldResolver(type => User)
205
+ async updater(@Root() productDetail: ProductDetail): Promise<User> {
206
+ return await getRepository(User).findOneBy({ id: productDetail.updater.id })
207
+ }
208
+
209
+ @FieldResolver(type => User)
210
+ async creator(@Root() productDetail: ProductDetail): Promise<User> {
211
+ return await getRepository(User).findOneBy({ id: productDetail.creator.id })
212
+ }
213
+ }