@things-factory/worksheet-base 4.3.769 → 4.3.770

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist-server/constants/template.js +2 -1
  2. package/dist-server/constants/template.js.map +1 -1
  3. package/dist-server/controllers/inbound/unloading-worksheet-controller.js +17 -0
  4. package/dist-server/controllers/inbound/unloading-worksheet-controller.js.map +1 -1
  5. package/dist-server/controllers/index.js +1 -0
  6. package/dist-server/controllers/index.js.map +1 -1
  7. package/dist-server/controllers/outbound/loading-worksheet-controller.js +53 -0
  8. package/dist-server/controllers/outbound/loading-worksheet-controller.js.map +1 -1
  9. package/dist-server/controllers/render-packing-label.js +160 -0
  10. package/dist-server/controllers/render-packing-label.js.map +1 -0
  11. package/dist-server/graphql/resolvers/worksheet/loading/create-group-loading-packages.js +20 -0
  12. package/dist-server/graphql/resolvers/worksheet/loading/create-group-loading-packages.js.map +1 -0
  13. package/dist-server/graphql/resolvers/worksheet/loading/index.js +2 -1
  14. package/dist-server/graphql/resolvers/worksheet/loading/index.js.map +1 -1
  15. package/dist-server/graphql/resolvers/worksheet/picking-worksheet.js +6 -1
  16. package/dist-server/graphql/resolvers/worksheet/picking-worksheet.js.map +1 -1
  17. package/dist-server/graphql/resolvers/worksheet/putaway-replenishment-worksheet.js +2 -1
  18. package/dist-server/graphql/resolvers/worksheet/putaway-replenishment-worksheet.js.map +1 -1
  19. package/dist-server/graphql/resolvers/worksheet/putaway-returning-worksheet.js +2 -1
  20. package/dist-server/graphql/resolvers/worksheet/putaway-returning-worksheet.js.map +1 -1
  21. package/dist-server/graphql/resolvers/worksheet/putaway-worksheet.js +1 -0
  22. package/dist-server/graphql/resolvers/worksheet/putaway-worksheet.js.map +1 -1
  23. package/dist-server/graphql/resolvers/worksheet/return-worksheet.js +2 -1
  24. package/dist-server/graphql/resolvers/worksheet/return-worksheet.js.map +1 -1
  25. package/dist-server/graphql/types/worksheet/group-loading-package-item.js +11 -0
  26. package/dist-server/graphql/types/worksheet/group-loading-package-item.js.map +1 -0
  27. package/dist-server/graphql/types/worksheet/index.js +8 -1
  28. package/dist-server/graphql/types/worksheet/index.js.map +1 -1
  29. package/dist-server/graphql/types/worksheet/worksheet-detail-info.js +1 -0
  30. package/dist-server/graphql/types/worksheet/worksheet-detail-info.js.map +1 -1
  31. package/dist-server/routes.js +11 -0
  32. package/dist-server/routes.js.map +1 -1
  33. package/package.json +13 -13
  34. package/server/constants/template.ts +2 -1
  35. package/server/controllers/inbound/unloading-worksheet-controller.ts +20 -0
  36. package/server/controllers/index.ts +1 -0
  37. package/server/controllers/outbound/loading-worksheet-controller.ts +67 -0
  38. package/server/controllers/render-packing-label.ts +199 -0
  39. package/server/graphql/resolvers/worksheet/loading/create-group-loading-packages.ts +32 -0
  40. package/server/graphql/resolvers/worksheet/loading/index.ts +3 -1
  41. package/server/graphql/resolvers/worksheet/picking-worksheet.ts +5 -1
  42. package/server/graphql/resolvers/worksheet/putaway-replenishment-worksheet.ts +2 -1
  43. package/server/graphql/resolvers/worksheet/putaway-returning-worksheet.ts +2 -1
  44. package/server/graphql/resolvers/worksheet/putaway-worksheet.ts +1 -0
  45. package/server/graphql/resolvers/worksheet/return-worksheet.ts +2 -1
  46. package/server/graphql/types/worksheet/group-loading-package-item.ts +8 -0
  47. package/server/graphql/types/worksheet/index.ts +8 -1
  48. package/server/graphql/types/worksheet/worksheet-detail-info.ts +1 -0
  49. package/server/routes.ts +11 -0
@@ -0,0 +1,199 @@
1
+ import FormData from 'form-data'
2
+ import fetch from 'node-fetch'
3
+ import { getRepository, In, IsNull } from 'typeorm'
4
+
5
+ import { Attachment, STORAGE } from '@things-factory/attachment-base'
6
+ import { User } from '@things-factory/auth-base'
7
+ import { config } from '@things-factory/env'
8
+ import { Domain } from '@things-factory/shell'
9
+ import { LoadingPackages } from '@things-factory/sales-base'
10
+
11
+ import { TEMPLATE_TYPE } from '../constants'
12
+
13
+ const REPORT_API_URL = config.get('reportApiUrl', 'http://localhost:8888/rest/report/show_html')
14
+
15
+ interface RenderPackingLabelInput {
16
+ releaseGoodNos?: string[]
17
+ loadingPackageIds?: string[]
18
+ printQuantity: number
19
+ }
20
+
21
+ export async function renderPackingLabel({ data }: { data: RenderPackingLabelInput }, context: any) {
22
+ const { domain }: { domain: Domain } = context.state
23
+ const { releaseGoodNos, loadingPackageIds, printQuantity = 1 } = data
24
+
25
+ if (!releaseGoodNos?.length && !loadingPackageIds?.length) {
26
+ throw new Error('No valid RO/loading package found')
27
+ }
28
+
29
+ const foundTemplate: Attachment = await getRepository(Attachment).findOne({
30
+ where: { domain, category: TEMPLATE_TYPE.PACKING_LABEL_TEMPLATE }
31
+ })
32
+
33
+ if (!foundTemplate) {
34
+ throw new Error('Packing label settings error. Please contact Support.')
35
+ }
36
+
37
+ const template = await STORAGE.readFile(foundTemplate.path, 'utf-8')
38
+
39
+ const foundLogo: Attachment = await getRepository(Attachment).findOne({
40
+ where: { domain, category: TEMPLATE_TYPE.LOGO }
41
+ })
42
+
43
+ let logo = null
44
+ if (foundLogo?.path) {
45
+ logo = 'data:' + foundLogo.mimetype + ';base64,' + (await STORAGE.readFile(foundLogo.path, 'base64'))
46
+ }
47
+
48
+ let loadingPackages: LoadingPackages[]
49
+
50
+ if (loadingPackageIds?.length) {
51
+ // Fetch by loading package IDs directly (for history popup reprinting)
52
+ loadingPackages = await getRepository(LoadingPackages).find({
53
+ where: {
54
+ domain,
55
+ id: In(loadingPackageIds),
56
+ deletedAt: IsNull()
57
+ },
58
+ relations: [
59
+ 'releaseGood',
60
+ 'releaseGood.bizplace',
61
+ 'loadingPackageItems',
62
+ 'loadingPackageItems.orderProduct',
63
+ 'loadingPackageItems.orderProduct.product'
64
+ ],
65
+ order: {
66
+ releaseGood: { createdAt: 'ASC' }
67
+ }
68
+ })
69
+
70
+ if (loadingPackages.length !== loadingPackageIds.length) {
71
+ throw new Error(`Loading package(s) not found, please refresh the list and try again`)
72
+ }
73
+
74
+ // Validate order status for the found packages
75
+ const releaseGoodIds = [...new Set(loadingPackages.map(lp => lp.releaseGood?.id).filter(Boolean))] as string[]
76
+ if (releaseGoodIds.length > 0) {
77
+ await validateReleaseGoodStatus(domain.id, releaseGoodIds, 'id')
78
+ }
79
+ } else {
80
+ // Fetch by release good numbers (original flow for preview print)
81
+ await validateReleaseGoodStatus(domain.id, releaseGoodNos!, 'name')
82
+
83
+ loadingPackages = await getRepository(LoadingPackages).find({
84
+ where: {
85
+ domain,
86
+ releaseGood: { name: In(releaseGoodNos!) },
87
+ deletedAt: IsNull()
88
+ },
89
+ relations: [
90
+ 'releaseGood',
91
+ 'releaseGood.bizplace',
92
+ 'loadingPackageItems',
93
+ 'loadingPackageItems.orderProduct',
94
+ 'loadingPackageItems.orderProduct.product'
95
+ ],
96
+ order: {
97
+ releaseGood: { createdAt: 'ASC' }
98
+ }
99
+ })
100
+ }
101
+
102
+ if (!loadingPackages.length) {
103
+ throw new Error('Unable to print due to no packages found for the selected orders')
104
+ }
105
+
106
+ const renderedPages: string[] = []
107
+
108
+ for (const loadingPackage of loadingPackages) {
109
+ const releaseGood = loadingPackage.releaseGood
110
+ const packageItems = loadingPackage.loadingPackageItems || []
111
+
112
+ const product_list = packageItems.map(item => ({
113
+ product_sku: item.orderProduct?.product?.sku || '',
114
+ product_name: item.orderProduct?.product?.name || item.orderProduct?.name || '',
115
+ product_qty: item.packedQty || 0
116
+ }))
117
+
118
+ const pageData = {
119
+ logo_url: logo,
120
+ company_name: releaseGood?.bizplace?.name || '',
121
+ ref_no: releaseGood?.refNo || '',
122
+ packed_qty: (loadingPackage.name || '').toUpperCase(),
123
+ product_list
124
+ }
125
+
126
+ const formData = new FormData()
127
+ formData.append('template', template)
128
+ formData.append('jsonString', JSON.stringify(pageData))
129
+
130
+ const response = await fetch(REPORT_API_URL, {
131
+ method: 'POST',
132
+ body: formData
133
+ })
134
+
135
+ const pageHtml = await response.text()
136
+
137
+ for (let i = 0; i < printQuantity; i++) {
138
+ renderedPages.push(pageHtml)
139
+ }
140
+ }
141
+
142
+ // Update printedAt and printedBy for all loading packages
143
+ const user: User = context.state.user
144
+ await getRepository(LoadingPackages).update(
145
+ { id: In(loadingPackages.map(lp => lp.id)) },
146
+ {
147
+ printedAt: new Date(),
148
+ printedBy: user
149
+ }
150
+ )
151
+
152
+ const combinedHtml = `
153
+ <!DOCTYPE html>
154
+ <html>
155
+ <head>
156
+ <title>Packing Labels</title>
157
+ <style>
158
+ @media print {
159
+ .page-break { page-break-after: always; }
160
+ .page-break:last-child { page-break-after: auto; }
161
+ }
162
+ </style>
163
+ </head>
164
+ <body>
165
+ ${renderedPages.map((html, idx) => `<div class="page-break">${html}</div>`).join('')}
166
+ </body>
167
+ </html>
168
+ `
169
+
170
+ return combinedHtml
171
+ }
172
+
173
+ async function validateReleaseGoodStatus(
174
+ domainId: string,
175
+ values: string[],
176
+ searchBy: 'id' | 'name'
177
+ ): Promise<void> {
178
+ const whereClause = searchBy === 'id' ? 'rg.id = ANY($2)' : 'rg.name = ANY($2)'
179
+
180
+ const validationResult: any[] = await getRepository(LoadingPackages).query(
181
+ `
182
+ SELECT rg.name, rg.status as rg_status, ws.status as ws_status
183
+ FROM release_goods rg
184
+ LEFT JOIN worksheets ws ON ws.release_good_id = rg.id AND ws.type = 'LOADING'
185
+ WHERE rg.domain_id = $1 AND ${whereClause}
186
+ `,
187
+ [domainId, values]
188
+ )
189
+
190
+ const invalidOrders = validationResult.filter(
191
+ r =>
192
+ !['LOADING', 'PROCESSING', 'PARTIAL_PROCESSING'].includes(r.rg_status) ||
193
+ !['EXECUTING', 'PARTIAL_EXECUTING'].includes(r.ws_status)
194
+ )
195
+
196
+ if (invalidOrders.length > 0) {
197
+ throw new Error(`Unable to print: Invalid order status for ${invalidOrders.map(o => o.name).join(', ')}`)
198
+ }
199
+ }
@@ -0,0 +1,32 @@
1
+ import { EntityManager } from 'typeorm'
2
+ import { User } from '@things-factory/auth-base'
3
+ import { ReleaseGood } from '@things-factory/sales-base'
4
+ import { Domain } from '@things-factory/shell'
5
+ import { LoadingWorksheetController } from '../../../../controllers'
6
+ import { WorksheetController } from '../../../../controllers'
7
+
8
+ export const createGroupLoadingPackagesResolver = {
9
+ async createGroupLoadingPackages(_: any, { releaseGoodNo, groupedItems }, context: any) {
10
+ const { tx, domain, user }: { tx: EntityManager; domain: Domain; user: User } = context.state
11
+
12
+ return await createGroupLoadingPackages(tx, domain, user, releaseGoodNo, groupedItems)
13
+ }
14
+ }
15
+
16
+ export async function createGroupLoadingPackages(
17
+ tx: EntityManager,
18
+ domain: Domain,
19
+ user: User,
20
+ releaseGoodNo: string,
21
+ groupedItems: Array<{ orderProductId: string; groupQty: number }>
22
+ ): Promise<any> {
23
+ const worksheetController: WorksheetController = new WorksheetController(tx, domain, user)
24
+ const releaseGood: ReleaseGood = await worksheetController.findRefOrder(
25
+ ReleaseGood,
26
+ { domain, name: releaseGoodNo },
27
+ ['bizplace']
28
+ )
29
+
30
+ const loadingWorksheetController: LoadingWorksheetController = new LoadingWorksheetController(tx, domain, user)
31
+ return await loadingWorksheetController.createGroupLoadingPackages(releaseGood, groupedItems)
32
+ }
@@ -3,12 +3,14 @@ import { loadingResolver } from './loading'
3
3
  import { undoLoadingResolver } from './undo-loading'
4
4
  import { completeLoadingResolver } from './complete-loading'
5
5
  import { validateQcSealsResolver } from './validate-qc-seals'
6
+ import { createGroupLoadingPackagesResolver } from './create-group-loading-packages'
6
7
 
7
8
  export const Mutations = {
8
9
  ...activateLoadingResolver,
9
10
  ...loadingResolver,
10
11
  ...undoLoadingResolver,
11
- ...completeLoadingResolver
12
+ ...completeLoadingResolver,
13
+ ...createGroupLoadingPackagesResolver
12
14
  }
13
15
 
14
16
  export const Query = {
@@ -163,7 +163,8 @@ export async function pickingWorksheet(domain: Domain, orderNo: String, location
163
163
 
164
164
  async function replenishmentWorksheet(orderNo: String, tx, domain, locationSortingRules) {
165
165
  let replenishment: Replenishment = await tx.getRepository(Replenishment).findOne({
166
- where: { domain, name: orderNo }
166
+ where: { domain, name: orderNo },
167
+ relations: ['bizplace', 'bizplace.domain', 'bizplace.company', 'bizplace.company.domain']
167
168
  })
168
169
 
169
170
  if (replenishment) {
@@ -200,6 +201,9 @@ async function replenishmentWorksheet(orderNo: String, tx, domain, locationSorti
200
201
 
201
202
  return {
202
203
  worksheetInfo: {
204
+ bizplaceName: replenishment.bizplace?.name,
205
+ partnerDomainId: replenishment.bizplace?.domain?.id,
206
+ customerCompanyDomainId: replenishment.bizplace?.company?.domain?.id,
203
207
  startedAt: worksheet.startedAt,
204
208
  replenishment
205
209
  },
@@ -106,7 +106,8 @@ export const putawayReplenishmentWorksheetResolver = {
106
106
  packingType: inventory.packingType,
107
107
  packingSize: inventory.packingSize,
108
108
  location: inventory.location,
109
- reusablePallet: inventory.reusablePallet
109
+ reusablePallet: inventory.reusablePallet,
110
+ conditionOfGoods: inventory.conditionOfGoods
110
111
  }
111
112
  })
112
113
  }
@@ -75,7 +75,8 @@ export const putawayReturningWorksheetResolver = {
75
75
  packingType: inventory.packingType,
76
76
  packingSize: inventory.packingSize,
77
77
  location: inventory.location,
78
- reusablePallet: inventory.reusablePallet
78
+ reusablePallet: inventory.reusablePallet,
79
+ conditionOfGoods: inventory.conditionOfGoods
79
80
  }
80
81
  })
81
82
  }
@@ -111,6 +111,7 @@ export async function findPutawayWorksheetByArrivalNoticeNo(arrivalNoticeNo, con
111
111
  product: product?.id ? product : null,
112
112
  location: location?.id ? location : null,
113
113
  reusablePallet: reusablePallet?.id ? reusablePallet : null,
114
+ conditionOfGoods: wd.inv_condition_of_goods,
114
115
  completed: wd.wd_status === WORKSHEET_STATUS.DONE,
115
116
  packingTypeSize: `${wd.inv_packing_type}(${wd.inv_packing_size})`,
116
117
  reusablePalletName: reusablePallet?.id ? reusablePallet.name : '',
@@ -64,7 +64,8 @@ export const returnWorksheetResolver = {
64
64
  targetName: targetInventory.name,
65
65
  packingType: inventory.packingType,
66
66
  packingSize: inventory.packingSize,
67
- location: inventory.location
67
+ location: inventory.location,
68
+ conditionOfGoods: inventory.conditionOfGoods
68
69
  }
69
70
  })
70
71
  }
@@ -0,0 +1,8 @@
1
+ import { gql } from 'apollo-server-koa'
2
+
3
+ export const GroupLoadingPackageItem = gql`
4
+ input GroupLoadingPackageItem {
5
+ orderProductId: String!
6
+ groupQty: Float!
7
+ }
8
+ `
@@ -12,6 +12,7 @@ import { GoodsDeliveryNote } from './goods-delivery-note'
12
12
  import { InventoryCheckWorksheet } from './inventory-check-worksheet'
13
13
  import { LoadedWorksheetDetail } from './loaded-worksheet-detail'
14
14
  import { LocationFilter } from './location-filter'
15
+ import { GroupLoadingPackageItem } from './group-loading-package-item'
15
16
  import { MultipleReleaseGoodWorksheet } from './multiple-release-good-worksheet'
16
17
  import { MyPickingAssignmentStatus } from './my-picking-assignment-status'
17
18
  import { NewWorksheet } from './new-worksheet'
@@ -342,6 +343,11 @@ export const Mutation = /* GraphQL */ `
342
343
  orderInfo: WorksheetPatch
343
344
  ): Boolean @privilege(category: "worksheet_execute", privilege: "mutation") @transaction
344
345
 
346
+ createGroupLoadingPackages (
347
+ releaseGoodNo: String!
348
+ groupedItems: [GroupLoadingPackageItem!]!
349
+ ): LoadingPackages @privilege(category: "worksheet_execute", privilege: "mutation") @transaction
350
+
345
351
  undoLoading (
346
352
  deliveryOrder: ObjectRef!
347
353
  palletIds: [String]!
@@ -902,5 +908,6 @@ export const Types = /* GraphQL */ [
902
908
  GenerateBatchPickInfo,
903
909
  MultipleReleaseGoodWorksheet,
904
910
  LocationFilter,
905
- ValidateQcSealsResult
911
+ ValidateQcSealsResult,
912
+ GroupLoadingPackageItem
906
913
  ]
@@ -81,5 +81,6 @@ export const WorksheetDetailInfo = gql`
81
81
  originLocation: Location
82
82
  customerCompanyDomainId: String
83
83
  grossWeight: Float
84
+ conditionOfGoods: String
84
85
  }
85
86
  `
package/server/routes.ts CHANGED
@@ -6,6 +6,7 @@ import { renderManifest } from './controllers/render-manifest'
6
6
  import { renderManualDO } from './controllers/render-manual-do'
7
7
  import { renderOrientageDO } from './controllers/render-orientage-do'
8
8
  import { renderOrientageGRN } from './controllers/render-orientage-grn'
9
+ import { renderPackingLabel } from './controllers/render-packing-label'
9
10
  import { renderRODO } from './controllers/render-ro-do'
10
11
  import { renderSeebuuGRN } from './controllers/render-seebuu-grn'
11
12
  import { renderFmGRN } from './controllers/render-fm-grn'
@@ -76,4 +77,14 @@ process.on('bootstrap-module-domain-private-route' as any, (app, routes) => {
76
77
  context.type = 'application/json'
77
78
  context.body = data
78
79
  })
80
+
81
+ routes.post('/render_packing_label', async (context, next) => {
82
+ try {
83
+ const data = context.request.body || {}
84
+ context.body = await renderPackingLabel({ data }, context)
85
+ } catch (error) {
86
+ context.status = 400
87
+ context.body = error.message || 'Failed to render packing label'
88
+ }
89
+ })
79
90
  })