@things-factory/sales-base 4.3.769 → 4.3.771

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 (58) hide show
  1. package/dist-server/service/draft-release-good/draft-release-good-create.js +138 -139
  2. package/dist-server/service/draft-release-good/draft-release-good-create.js.map +1 -1
  3. package/dist-server/service/index.js +16 -2
  4. package/dist-server/service/index.js.map +1 -1
  5. package/dist-server/service/loading-package-items/index.js +9 -0
  6. package/dist-server/service/loading-package-items/index.js.map +1 -0
  7. package/dist-server/service/loading-package-items/loading-package-items-mutation.js +120 -0
  8. package/dist-server/service/loading-package-items/loading-package-items-mutation.js.map +1 -0
  9. package/dist-server/service/loading-package-items/loading-package-items-query.js +87 -0
  10. package/dist-server/service/loading-package-items/loading-package-items-query.js.map +1 -0
  11. package/dist-server/service/loading-package-items/loading-package-items-type.js +77 -0
  12. package/dist-server/service/loading-package-items/loading-package-items-type.js.map +1 -0
  13. package/dist-server/service/loading-package-items/loading-package-items.js +173 -0
  14. package/dist-server/service/loading-package-items/loading-package-items.js.map +1 -0
  15. package/dist-server/service/loading-packages/index.js +9 -0
  16. package/dist-server/service/loading-packages/index.js.map +1 -0
  17. package/dist-server/service/loading-packages/loading-packages-mutation.js +151 -0
  18. package/dist-server/service/loading-packages/loading-packages-mutation.js.map +1 -0
  19. package/dist-server/service/loading-packages/loading-packages-query.js +316 -0
  20. package/dist-server/service/loading-packages/loading-packages-query.js.map +1 -0
  21. package/dist-server/service/loading-packages/loading-packages-type.js +77 -0
  22. package/dist-server/service/loading-packages/loading-packages-type.js.map +1 -0
  23. package/dist-server/service/loading-packages/loading-packages.js +138 -0
  24. package/dist-server/service/loading-packages/loading-packages.js.map +1 -0
  25. package/dist-server/service/order-inventory/order-inventory.js +1 -0
  26. package/dist-server/service/order-inventory/order-inventory.js.map +1 -1
  27. package/dist-server/service/release-good/release-good-mutation.js +48 -15
  28. package/dist-server/service/release-good/release-good-mutation.js.map +1 -1
  29. package/dist-server/service/release-good/release-good-query.js +61 -3
  30. package/dist-server/service/release-good/release-good-query.js.map +1 -1
  31. package/dist-server/service/release-good/release-good-types.js +15 -1
  32. package/dist-server/service/release-good/release-good-types.js.map +1 -1
  33. package/dist-server/service/release-good/release-good.js +4 -0
  34. package/dist-server/service/release-good/release-good.js.map +1 -1
  35. package/dist-server/utils/inventory-util.js +6 -2
  36. package/dist-server/utils/inventory-util.js.map +1 -1
  37. package/dist-server/utils/order-no-generator.js +8 -0
  38. package/dist-server/utils/order-no-generator.js.map +1 -1
  39. package/package.json +8 -8
  40. package/server/service/draft-release-good/draft-release-good-create.ts +201 -206
  41. package/server/service/index.ts +16 -2
  42. package/server/service/loading-package-items/index.ts +6 -0
  43. package/server/service/loading-package-items/loading-package-items-mutation.ts +112 -0
  44. package/server/service/loading-package-items/loading-package-items-query.ts +43 -0
  45. package/server/service/loading-package-items/loading-package-items-type.ts +45 -0
  46. package/server/service/loading-package-items/loading-package-items.ts +140 -0
  47. package/server/service/loading-packages/index.ts +6 -0
  48. package/server/service/loading-packages/loading-packages-mutation.ts +150 -0
  49. package/server/service/loading-packages/loading-packages-query.ts +272 -0
  50. package/server/service/loading-packages/loading-packages-type.ts +45 -0
  51. package/server/service/loading-packages/loading-packages.ts +110 -0
  52. package/server/service/order-inventory/order-inventory.ts +1 -0
  53. package/server/service/release-good/release-good-mutation.ts +49 -21
  54. package/server/service/release-good/release-good-query.ts +103 -36
  55. package/server/service/release-good/release-good-types.ts +9 -0
  56. package/server/service/release-good/release-good.ts +5 -1
  57. package/server/utils/inventory-util.ts +7 -2
  58. package/server/utils/order-no-generator.ts +12 -0
@@ -0,0 +1,272 @@
1
+ import { Resolver, Query, FieldResolver, Root, Args, Arg, Ctx, Directive, Int } from 'type-graphql'
2
+ import { getRepository, In, IsNull } from 'typeorm'
3
+ import { Domain, ListParam, convertListParams } from '@things-factory/shell'
4
+ import { User } from '@things-factory/auth-base'
5
+ import { ReleaseGood } from '../'
6
+ import { LoadingPackages } from './loading-packages'
7
+ import { LoadingPackagesList } from './loading-packages-type'
8
+
9
+ @Resolver(LoadingPackages)
10
+ export class LoadingPackagesQuery {
11
+ @Query(returns => LoadingPackages, { description: 'To fetch a LoadingPackages' })
12
+ async loadingPackage(@Arg('id') id: string, @Ctx() context: any): Promise<LoadingPackages> {
13
+ const { domain } = context.state
14
+
15
+ return await getRepository(LoadingPackages).findOne({
16
+ where: { domain, id, deletedAt: IsNull() }
17
+ })
18
+ }
19
+
20
+ @Query(returns => LoadingPackagesList, { description: 'To fetch multiple LoadingPackages' })
21
+ async loadingPackages(@Args() params: ListParam, @Ctx() context: any): Promise<LoadingPackagesList> {
22
+ const { domain } = context.state
23
+
24
+ const convertedParams = convertListParams(params, domain.id)
25
+ const [items, total] = await getRepository(LoadingPackages).findAndCount(convertedParams)
26
+
27
+ return { items, total }
28
+ }
29
+
30
+ @Query(returns => [LoadingPackages], { description: 'To fetch LoadingPackages by releaseGoodNo' })
31
+ async loadingPackagesByReleaseGoodNo(@Arg('releaseGoodNo') releaseGoodNo: string, @Ctx() context: any): Promise<LoadingPackages[]> {
32
+ const { domain } = context.state
33
+
34
+ const releaseGood = await getRepository(ReleaseGood).findOne({
35
+ where: { domain, name: releaseGoodNo }
36
+ })
37
+
38
+ if (!releaseGood) {
39
+ return []
40
+ }
41
+
42
+ return await getRepository(LoadingPackages).find({
43
+ where: { domain, releaseGood, deletedAt: IsNull() },
44
+ relations: [
45
+ 'loadingPackageItems',
46
+ 'loadingPackageItems.orderProduct',
47
+ 'loadingPackageItems.orderProduct.product',
48
+ 'loadingPackageItems.productDetail',
49
+ 'releaseGood',
50
+ 'bizplace'
51
+ ],
52
+ order: { createdAt: 'ASC' }
53
+ })
54
+ }
55
+
56
+ @Query(returns => [LoadingPackages], { description: 'To fetch LoadingPackages by multiple releaseGoodNos' })
57
+ async loadingPackagesByReleaseGoodNos(
58
+ @Arg('releaseGoodNos', type => [String]) releaseGoodNos: string[],
59
+ @Ctx() context: any
60
+ ): Promise<LoadingPackages[]> {
61
+ const { domain } = context.state
62
+
63
+ // Step 1: Check if releaseGoodNos is empty
64
+ if (!releaseGoodNos || releaseGoodNos.length === 0) {
65
+ throw new Error('No release goods numbers provided')
66
+ }
67
+
68
+ // Step 2: Make them into distinct values
69
+ const distinctReleaseGoodNos = [...new Set(releaseGoodNos.filter(no => no && no.trim()))]
70
+
71
+ if (distinctReleaseGoodNos.length === 0) {
72
+ throw new Error('No valid release goods numbers provided')
73
+ }
74
+
75
+ // Step 3: Use distinct array to search in the DB
76
+ const releaseGoods = await getRepository(ReleaseGood).find({
77
+ where: { domain, name: In(distinctReleaseGoodNos) }
78
+ })
79
+
80
+ // Step 4: Check if the length returned matches the distinct array
81
+ if (releaseGoods.length !== distinctReleaseGoodNos.length) {
82
+ const foundNames = releaseGoods.map(rg => rg.name)
83
+ const notFoundNames = distinctReleaseGoodNos.filter(name => !foundNames.includes(name))
84
+ throw new Error(`Release good number(s) not found: ${notFoundNames.join(', ')}`)
85
+ }
86
+
87
+ // Step 5: Fetch loading packages and loading package items
88
+ const releaseGoodIds = releaseGoods.map(rg => rg.id)
89
+
90
+ return await getRepository(LoadingPackages)
91
+ .createQueryBuilder('lp')
92
+ .leftJoinAndSelect('lp.loadingPackageItems', 'lpi')
93
+ .leftJoinAndSelect('lpi.orderProduct', 'op')
94
+ .leftJoinAndSelect('op.product', 'product')
95
+ .leftJoinAndSelect('lpi.productDetail', 'pd')
96
+ .leftJoinAndSelect('lp.releaseGood', 'rg')
97
+ .leftJoinAndSelect('rg.bizplace', 'bizplace')
98
+ .where('lp.domain_id = :domainId', { domainId: domain.id })
99
+ .andWhere('lp.release_good_id IN (:...releaseGoodIds)', { releaseGoodIds })
100
+ .andWhere('lp.deleted_at IS NULL')
101
+ .andWhere('lpi.deleted_at IS NULL')
102
+ .orderBy('lp.release_good_id', 'ASC')
103
+ .addOrderBy('lp.created_at', 'ASC')
104
+ .getMany()
105
+ }
106
+
107
+ @Query(returns => LoadingPackagesList, { description: 'To fetch printed LoadingPackages history with pagination' })
108
+ async printedLoadingPackagesHistory(@Args() params: ListParam, @Ctx() context: any): Promise<LoadingPackagesList> {
109
+ const { domain } = context.state
110
+
111
+ const convertedParams = convertListParams(params, domain.id)
112
+
113
+ // Extract filters from params
114
+ const orderNoFilter = params.filters?.find(f => f.name === 'releaseGood.name')
115
+ const displayNumberFilter = params.filters?.find(f => f.name === 'displayNumber')
116
+
117
+ // Build parameters for raw query
118
+ const queryParams: any[] = [domain.id]
119
+ let paramIndex = 2
120
+
121
+ // Build WHERE clauses for filters
122
+ let orderNoClause = ''
123
+ if (orderNoFilter?.value) {
124
+ orderNoClause = `AND (rg.name ILIKE $${paramIndex} OR rg.ref_no ILIKE $${paramIndex})`
125
+ queryParams.push(`%${orderNoFilter.value}%`)
126
+ paramIndex++
127
+ }
128
+
129
+ let displayNumberClause = ''
130
+ if (displayNumberFilter?.value) {
131
+ const displayNumber = parseInt(displayNumberFilter.value)
132
+ if (!isNaN(displayNumber)) {
133
+ displayNumberClause = `AND dn.display_number = $${paramIndex}`
134
+ queryParams.push(displayNumber)
135
+ paramIndex++
136
+ }
137
+ }
138
+
139
+ // Build sorting - map frontend column names to subquery column names
140
+ const columnMapping: Record<string, string> = {
141
+ releaseGood: 'rg_name',
142
+ refNo: 'rg_ref_no',
143
+ displayNumber: 'display_number',
144
+ printedAt: 'printed_at',
145
+ printedBy: 'user_name'
146
+ }
147
+
148
+ let orderByClause = 'ORDER BY dn.printed_at DESC'
149
+ if (convertedParams.order) {
150
+ const sortParts = Object.entries(convertedParams.order)
151
+ .map(([key, direction]) => {
152
+ const mappedColumn = columnMapping[key] || key
153
+ return `dn.${mappedColumn} ${direction}`
154
+ })
155
+ if (sortParts.length > 0) {
156
+ orderByClause = `ORDER BY ${sortParts.join(', ')}`
157
+ }
158
+ }
159
+
160
+ // Pagination
161
+ const skip = convertedParams.skip || 0
162
+ const take = convertedParams.take || 50
163
+
164
+ // Single query: Get all data with display numbers and total count using window functions
165
+ const query = `
166
+ SELECT
167
+ dn.id,
168
+ dn.name,
169
+ dn.printed_at as "printedAt",
170
+ dn.display_number as "displayNumber",
171
+ COUNT(*) OVER() as total_count,
172
+ dn.rg_id as "releaseGood_id",
173
+ dn.rg_name as "releaseGood_name",
174
+ dn.rg_ref_no as "releaseGood_refNo",
175
+ dn.user_id as "printedBy_id",
176
+ dn.user_name as "printedBy_name",
177
+ dn.user_description as "printedBy_description"
178
+ FROM (
179
+ SELECT
180
+ lp.id,
181
+ lp.name,
182
+ lp.printed_at,
183
+ ROW_NUMBER() OVER (PARTITION BY lp.release_good_id ORDER BY lp.created_at ASC) as display_number,
184
+ rg.id as rg_id,
185
+ rg.name as rg_name,
186
+ rg.ref_no as rg_ref_no,
187
+ u.id as user_id,
188
+ u.name as user_name,
189
+ u.description as user_description
190
+ FROM loading_packages lp
191
+ LEFT JOIN release_goods rg ON rg.id = lp.release_good_id
192
+ LEFT JOIN users u ON u.id = lp.printed_by_id
193
+ WHERE lp.domain_id = $1
194
+ AND lp.deleted_at IS NULL
195
+ AND lp.printed_at IS NOT NULL
196
+ ${orderNoClause}
197
+ ) dn
198
+ WHERE 1=1 ${displayNumberClause}
199
+ ${orderByClause}
200
+ LIMIT ${take} OFFSET ${skip}
201
+ `
202
+
203
+ const results: any[] = await getRepository(LoadingPackages).query(query, queryParams)
204
+
205
+ if (results.length === 0) {
206
+ return { items: [], total: 0 }
207
+ }
208
+
209
+ // Get total from first row (same for all rows due to COUNT(*) OVER())
210
+ const total = parseInt(results[0]?.total_count) || 0
211
+
212
+ // Map raw results to entity structure
213
+ const items = results.map(row => ({
214
+ id: row.id,
215
+ name: row.name,
216
+ printedAt: row.printedAt,
217
+ displayNumber: parseInt(row.displayNumber) || 0,
218
+ releaseGood: row.releaseGood_id ? {
219
+ id: row.releaseGood_id,
220
+ name: row.releaseGood_name,
221
+ refNo: row.releaseGood_refNo
222
+ } : null,
223
+ printedBy: row.printedBy_id ? {
224
+ id: row.printedBy_id,
225
+ name: row.printedBy_name,
226
+ description: row.printedBy_description
227
+ } : null
228
+ })) as LoadingPackages[]
229
+
230
+ return { items, total }
231
+ }
232
+
233
+ @Query(returns => Int, { description: 'To get the maximum packing label count across all release goods' })
234
+ async maxPackingLabelCount(@Ctx() context: any): Promise<number> {
235
+ const { domain } = context.state
236
+
237
+ const result = await getRepository(LoadingPackages)
238
+ .createQueryBuilder('lp')
239
+ .select('rg.id', 'releaseGoodId')
240
+ .addSelect('COUNT(lp.id)', 'count')
241
+ .leftJoin('lp.releaseGood', 'rg')
242
+ .where('lp.domain_id = :domainId', { domainId: domain.id })
243
+ .andWhere('lp.deleted_at IS NULL')
244
+ .andWhere('lp.printed_at IS NOT NULL')
245
+ .groupBy('rg.id')
246
+ .orderBy('count', 'DESC')
247
+ .limit(1)
248
+ .getRawOne()
249
+
250
+ return result ? parseInt(result.count) : 0
251
+ }
252
+
253
+ @FieldResolver(type => Domain)
254
+ async domain(@Root() loadingPackages: LoadingPackages): Promise<Domain> {
255
+ return await getRepository(Domain).findOne(loadingPackages.domainId)
256
+ }
257
+
258
+ @FieldResolver(type => User)
259
+ async updater(@Root() loadingPackages: LoadingPackages): Promise<User> {
260
+ return await getRepository(User).findOne(loadingPackages.updaterId)
261
+ }
262
+
263
+ @FieldResolver(type => User)
264
+ async creator(@Root() loadingPackages: LoadingPackages): Promise<User> {
265
+ return await getRepository(User).findOne(loadingPackages.creatorId)
266
+ }
267
+
268
+ @FieldResolver(type => User)
269
+ async printedBy(@Root() loadingPackages: LoadingPackages): Promise<User> {
270
+ return await getRepository(User).findOne(loadingPackages.printedById)
271
+ }
272
+ }
@@ -0,0 +1,45 @@
1
+ import { ObjectType, Field, InputType, Int, ID, registerEnumType } from 'type-graphql'
2
+
3
+ import { LoadingPackages } from './loading-packages'
4
+
5
+ @InputType()
6
+ export class NewLoadingPackages {
7
+ @Field()
8
+ name: string
9
+
10
+ @Field({ nullable: true })
11
+ description?: string
12
+
13
+ @Field({ nullable: true })
14
+ active?: boolean
15
+
16
+ @Field({ nullable: true })
17
+ params?: string
18
+ }
19
+
20
+ @InputType()
21
+ export class LoadingPackagesPatch {
22
+ @Field(type => ID, { nullable: true })
23
+ id?: string
24
+
25
+ @Field({ nullable: true })
26
+ name?: string
27
+
28
+ @Field({ nullable: true })
29
+ description?: string
30
+
31
+ @Field({ nullable: true })
32
+ active?: boolean
33
+
34
+ @Field()
35
+ cuFlag: string
36
+ }
37
+
38
+ @ObjectType()
39
+ export class LoadingPackagesList {
40
+ @Field(type => [LoadingPackages])
41
+ items: LoadingPackages[]
42
+
43
+ @Field(type => Int)
44
+ total: number
45
+ }
@@ -0,0 +1,110 @@
1
+ import {
2
+ CreateDateColumn,
3
+ UpdateDateColumn,
4
+ Entity,
5
+ Index,
6
+ Column,
7
+ RelationId,
8
+ OneToMany,
9
+ ManyToOne,
10
+ PrimaryGeneratedColumn
11
+ } from 'typeorm'
12
+ import { ObjectType, Field, Int, ID, registerEnumType } from 'type-graphql'
13
+
14
+ import { Domain } from '@things-factory/shell'
15
+ import { User } from '@things-factory/auth-base'
16
+ import { Bizplace } from '@things-factory/biz-base'
17
+ import { ReleaseGood } from '../'
18
+ import { LoadingPackageItems } from '../loading-package-items/loading-package-items'
19
+
20
+ @Entity()
21
+ @Index('ix_loading_packages_0', (loadingPackages: LoadingPackages) => [loadingPackages.domain, loadingPackages.name], { unique: true })
22
+ @ObjectType({ description: 'Entity for LoadingPackages' })
23
+ export class LoadingPackages {
24
+ @PrimaryGeneratedColumn('uuid')
25
+ @Field(type => ID)
26
+ readonly id: string
27
+
28
+ @Column()
29
+ @Field()
30
+ name: string
31
+
32
+ @Column({ nullable: true })
33
+ @Field({ nullable: true })
34
+ status: string
35
+
36
+ @ManyToOne(type => Domain)
37
+ @Field({ nullable: true })
38
+ domain?: Domain
39
+
40
+ @RelationId((loadingPackages: LoadingPackages) => loadingPackages.domain)
41
+ domainId?: string
42
+
43
+ @ManyToOne(type => Bizplace)
44
+ @Field(type => Bizplace, { nullable: true })
45
+ bizplace?: Bizplace
46
+
47
+ @RelationId((loadingPackages: LoadingPackages) => loadingPackages.bizplace)
48
+ bizplaceId?: string
49
+
50
+ @ManyToOne(type => ReleaseGood)
51
+ @Field(type => ReleaseGood, { nullable: true })
52
+ releaseGood?: ReleaseGood
53
+
54
+ @OneToMany(type => LoadingPackageItems, loadingPackageItems => loadingPackageItems.loadingPackage)
55
+ @Field(type => [LoadingPackageItems], { nullable: true })
56
+ loadingPackageItems: LoadingPackageItems[]
57
+
58
+ @CreateDateColumn()
59
+ @Field({ nullable: true })
60
+ createdAt?: Date
61
+
62
+ @UpdateDateColumn()
63
+ @Field({ nullable: true })
64
+ updatedAt?: Date
65
+
66
+ @ManyToOne(type => User, {
67
+ nullable: true
68
+ })
69
+ @Field({ nullable: true })
70
+ creator?: User
71
+
72
+ @RelationId((loadingPackages: LoadingPackages) => loadingPackages.creator)
73
+ creatorId?: string
74
+
75
+ @ManyToOne(type => User, {
76
+ nullable: true
77
+ })
78
+ @Field({ nullable: true })
79
+ updater?: User
80
+
81
+ @RelationId((loadingPackages: LoadingPackages) => loadingPackages.updater)
82
+ updaterId?: string
83
+
84
+ @ManyToOne(type => User, {
85
+ nullable: true
86
+ })
87
+ @Field({ nullable: true })
88
+ printedBy?: User
89
+
90
+ @RelationId((loadingPackages: LoadingPackages) => loadingPackages.printedBy)
91
+ printedById?: string
92
+
93
+ @Column({ nullable: true })
94
+ @Field({ nullable: true })
95
+ printedAt?: Date
96
+
97
+ @Column({ nullable: true })
98
+ @Field({ nullable: true })
99
+ deletedAt?: Date
100
+
101
+ @ManyToOne(type => User, { nullable: true })
102
+ @Field({ nullable: true })
103
+ deletedBy?: User
104
+
105
+ @RelationId((loadingPackages: LoadingPackages) => loadingPackages.deletedBy)
106
+ deletedById?: string
107
+
108
+ @Field(type => Int, { nullable: true })
109
+ displayNumber?: number
110
+ }
@@ -227,6 +227,7 @@ export class OrderInventory {
227
227
  orderProduct: OrderProduct
228
228
 
229
229
  @RelationId((orderInventory: OrderInventory) => orderInventory.orderProduct)
230
+ @Field({ nullable: true })
230
231
  orderProductId: string
231
232
 
232
233
  @OneToMany(type => OrderToteItem, orderToteItem => orderToteItem.orderInventory, { nullable: true })
@@ -476,14 +476,19 @@ export async function deleteReleaseGood(tx: EntityManager, name: string, user: U
476
476
  await Promise.all(
477
477
  foundOIs.map(async (oi: OrderInventory) => {
478
478
  if (oi?.inventory?.id) {
479
- oi.inventory = await tx.getRepository(Inventory).findOne(oi.inventory.id)
480
-
481
- await tx.getRepository(Inventory).save({
482
- ...oi.inventory,
483
- lockedQty: safeDecimalOperation(oi.inventory.lockedQty, oi.releaseQty, 'subtract'),
484
- lockedUomValue: safeDecimalOperation(oi.inventory.lockedUomValue, oi.releaseUomValue, 'subtract'),
485
- updater: user
486
- })
479
+ await tx
480
+ .getRepository(Inventory)
481
+ .createQueryBuilder()
482
+ .update(Inventory)
483
+ .set({
484
+ lockedQty: () => `GREATEST(COALESCE("locked_qty", 0) - :releaseQty::numeric, 0)`,
485
+ lockedUomValue: () => `GREATEST(COALESCE("locked_uom_value", 0) - :releaseUomValue::numeric, 0)`,
486
+ updater: user
487
+ })
488
+ .setParameter('releaseQty', oi.releaseQty)
489
+ .setParameter('releaseUomValue', oi.releaseUomValue)
490
+ .where('id = :id', { id: oi.inventory.id })
491
+ .execute()
487
492
  }
488
493
 
489
494
  await tx.getRepository(OrderInventory).delete({ id: oi.id })
@@ -505,9 +510,11 @@ export async function deleteReleaseGood(tx: EntityManager, name: string, user: U
505
510
  .createQueryBuilder()
506
511
  .update(ProductDetailStock)
507
512
  .set({
508
- unassignedQty: () => `"unassigned_qty" - ${op.releaseQty}`,
509
- unassignedUomValue: () => `"unassigned_uom_value" - ${op.releaseUomValue}`
513
+ unassignedQty: () => `GREATEST("unassigned_qty" - :releaseQty::numeric, 0)`,
514
+ unassignedUomValue: () => `GREATEST("unassigned_uom_value" - :releaseUomValue::numeric, 0)`
510
515
  })
516
+ .setParameter('releaseQty', op.releaseQty)
517
+ .setParameter('releaseUomValue', op.releaseUomValue)
511
518
  .where({ productDetail: op.productDetail.id })
512
519
  .execute()
513
520
 
@@ -1131,20 +1138,37 @@ export async function generateReleaseGoodFunction(
1131
1138
  if (worksheetPickingAssignment?.value !== 'true' || oi.inventory?.id) {
1132
1139
  await tx.getRepository(OrderInventory).save({ ...oi, name: OrderNoGenerator.orderInventory() })
1133
1140
 
1134
- await tx.getRepository(Inventory).update(oi.inventory.id, {
1135
- lockedQty: (oi.inventory?.lockedQty || 0) + oi.releaseQty,
1136
- lockedUomValue: (oi.inventory?.lockedUomValue || 0) + oi.releaseUomValue,
1137
- updater: user
1138
- })
1141
+ const lockResult = await tx
1142
+ .getRepository(Inventory)
1143
+ .createQueryBuilder()
1144
+ .update(Inventory)
1145
+ .set({
1146
+ lockedQty: () => `COALESCE("locked_qty", 0) + :releaseQty::numeric`,
1147
+ lockedUomValue: () => `COALESCE("locked_uom_value", 0) + :releaseUomValue::numeric`,
1148
+ updater: user
1149
+ })
1150
+ .setParameter('releaseQty', oi.releaseQty)
1151
+ .setParameter('releaseUomValue', oi.releaseUomValue)
1152
+ .where('id = :id AND qty >= COALESCE(locked_qty, 0) + :newQty', {
1153
+ id: oi.inventory.id,
1154
+ newQty: oi.releaseQty
1155
+ })
1156
+ .execute()
1157
+
1158
+ if (lockResult.affected === 0) {
1159
+ throw new Error(`Insufficient inventory for ${oi.product?.sku || 'unknown product'}`)
1160
+ }
1139
1161
  } else {
1140
1162
  await tx
1141
1163
  .getRepository(ProductDetailStock)
1142
1164
  .createQueryBuilder()
1143
1165
  .update(ProductDetailStock)
1144
1166
  .set({
1145
- unassignedQty: () => `"unassigned_qty" + ${oi.releaseQty}`,
1146
- unassignedUomValue: () => `"unassigned_uom_value" + ${oi.releaseUomValue}`
1167
+ unassignedQty: () => `"unassigned_qty" + :releaseQty::numeric`,
1168
+ unassignedUomValue: () => `"unassigned_uom_value" + :releaseUomValue::numeric`
1147
1169
  })
1170
+ .setParameter('releaseQty', oi.releaseQty)
1171
+ .setParameter('releaseUomValue', oi.releaseUomValue)
1148
1172
  .where({ productDetail: oi.productDetail.id })
1149
1173
  .execute()
1150
1174
  }
@@ -1971,10 +1995,12 @@ export async function bulkGenerateReleaseGood(
1971
1995
  .createQueryBuilder()
1972
1996
  .update(Inventory)
1973
1997
  .set({
1974
- lockedQty: () => `COALESCE("locked_qty",0) + ${generatedOI.releaseQty}`,
1975
- lockedUomValue: () => `COALESCE("locked_uom_value",0) + ${generatedOI.releaseUomValue}`,
1998
+ lockedQty: () => `COALESCE("locked_qty",0) + :releaseQty::numeric`,
1999
+ lockedUomValue: () => `COALESCE("locked_uom_value",0) + :releaseUomValue::numeric`,
1976
2000
  updater: user
1977
2001
  })
2002
+ .setParameter('releaseQty', generatedOI.releaseQty)
2003
+ .setParameter('releaseUomValue', generatedOI.releaseUomValue)
1978
2004
  .where(`id = :id AND qty >= COALESCE(locked_qty,0) + :newQty`, {
1979
2005
  id: oi.inventory.id,
1980
2006
  newQty: generatedOI.releaseQty
@@ -1990,9 +2016,11 @@ export async function bulkGenerateReleaseGood(
1990
2016
  .createQueryBuilder()
1991
2017
  .update(ProductDetailStock)
1992
2018
  .set({
1993
- unassignedQty: () => `"unassigned_qty" + ${generatedOI.releaseQty}`,
1994
- unassignedUomValue: () => `"unassigned_uom_value" + ${generatedOI.releaseUomValue}`
2019
+ unassignedQty: () => `"unassigned_qty" + :releaseQty::numeric`,
2020
+ unassignedUomValue: () => `"unassigned_uom_value" + :releaseUomValue::numeric`
1995
2021
  })
2022
+ .setParameter('releaseQty', generatedOI.releaseQty)
2023
+ .setParameter('releaseUomValue', generatedOI.releaseUomValue)
1996
2024
  .where({ productDetail: oi.productDetail ? oi.productDetail.id : oi.productDetailId })
1997
2025
  .execute()
1998
2026
  }