@things-factory/integration-sftp 8.0.0-beta.8 → 8.0.0

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/integration-sftp",
3
- "version": "8.0.0-beta.8",
3
+ "version": "8.0.0",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "client/index.js",
6
6
  "things-factory": true,
@@ -24,13 +24,13 @@
24
24
  "migration:create": "node ../../node_modules/typeorm/cli.js migration:create ./server/migrations/migration"
25
25
  },
26
26
  "dependencies": {
27
- "@things-factory/auth-base": "^8.0.0-beta.8",
28
- "@things-factory/biz-base": "^8.0.0-beta.8",
29
- "@things-factory/env": "^8.0.0-beta.4",
30
- "@things-factory/integration-fulfillment": "^8.0.0-beta.8",
31
- "@things-factory/shell": "^8.0.0-beta.5",
27
+ "@things-factory/auth-base": "^8.0.0",
28
+ "@things-factory/biz-base": "^8.0.0",
29
+ "@things-factory/env": "^8.0.0",
30
+ "@things-factory/integration-fulfillment": "^8.0.0",
31
+ "@things-factory/shell": "^8.0.0",
32
32
  "aws-sdk": "^2.960.0",
33
33
  "xml-js": "^1.6.11"
34
34
  },
35
- "gitHead": "bf5206511b2d84dfb95edc3dae7f54f6cbb9bcca"
35
+ "gitHead": "07ef27d272dd9a067a9648ac7013748510556a18"
36
36
  }
@@ -0,0 +1,51 @@
1
+ import { BACKUPPATH, SNSUBMITDATAPATH } from '../../../sftp-const'
2
+
3
+ export function createSerialNumber() {
4
+ return {
5
+ method: 'post',
6
+ path: '{folderPath}/{folderType}{snPath}',
7
+ denormalize(req) {
8
+ let { releaseGood, inventoryItems, sftp } = req
9
+ let { folderPath, platform } = sftp
10
+ const folderType: string = sftp.isDevelopment ? `dev` : `prd`
11
+ const snPath: string = SNSUBMITDATAPATH
12
+ const backupPath: string = `${BACKUPPATH}${platform}/${folderType}`
13
+
14
+ let responseFilePattern: any = JSON.parse(sftp.responseFilePattern)
15
+ const { snSuffix, fileExtension, snRunningNumberDigit, snSequence } = responseFilePattern
16
+ let newSeq: number = parseFloat(snSequence) + 1
17
+ let newSequence = newSeq.toString().padStart(snRunningNumberDigit, '0')
18
+ let newResponseFilePattern: string = JSON.stringify({
19
+ ...responseFilePattern,
20
+ snSequence: newSeq.toString()
21
+ })
22
+ sftp = { ...sftp, responseFilePattern: newResponseFilePattern }
23
+
24
+ const today = new Date()
25
+ const year = today.getFullYear().toString()
26
+ const month = (today.getMonth() + 1).toString().padStart(2, '0')
27
+ const day = today.getDate().toString().padStart(2, '0')
28
+ const newDate = day + month + year
29
+
30
+ let title: string = snSuffix ? snSuffix : ``
31
+ title += `_` + newDate
32
+ title += `_` + newSequence
33
+ title += fileExtension ? `.` + fileExtension : ``
34
+
35
+ let content: string = ''
36
+ const orderNo: string = releaseGood.refNo
37
+ for (let i = 0; i < inventoryItems.length; i++) {
38
+ let serialNumber: string = inventoryItems[i].serialNumber
39
+ content += orderNo + '|' + serialNumber + '\n'
40
+ }
41
+
42
+ return {
43
+ resource: { folderPath, folderType, snPath },
44
+ payload: { title, content, sftp, backupPath }
45
+ }
46
+ },
47
+ normalize(res) {
48
+ return res
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,88 @@
1
+ import { BACKUPPATH, COMPLETEDATAPATH } from '../../../sftp-const'
2
+
3
+ export function createShipment() {
4
+ return {
5
+ method: 'post',
6
+ path: '{folderPath}/{folderType}{shipmentCompletePath}',
7
+ denormalize(req) {
8
+ let { releaseGoods, sftp } = req
9
+ let { folderPath, platform } = sftp
10
+ const folderType: string = sftp.isDevelopment ? `dev` : `prd`
11
+ const shipmentCompletePath: string = COMPLETEDATAPATH
12
+ const backupPath: string = `${BACKUPPATH}${platform}/${folderType}`
13
+
14
+ let responseFilePattern: any = JSON.parse(sftp.responseFilePattern)
15
+ const { suffix, fileExtension, runningNumberDigit, sequence } = responseFilePattern
16
+ let newSeq: number = parseFloat(sequence) + 1
17
+ let newSequence = newSeq.toString().padStart(runningNumberDigit, '0')
18
+ let newResponseFilePattern: string = JSON.stringify({
19
+ ...responseFilePattern,
20
+ sequence: newSeq.toString()
21
+ })
22
+ sftp = { ...sftp, responseFilePattern: newResponseFilePattern }
23
+
24
+ const warehouseNumber: string = releaseGoods[0].refNo2
25
+ let title: string = suffix ? suffix : ``
26
+ title += newSequence
27
+ title += warehouseNumber ? `_` + warehouseNumber : ``
28
+ title += fileExtension ? `.` + fileExtension : ``
29
+
30
+ let backupTitle: string = releaseGoods[0].refNo + '_' + title
31
+ const orderNoColumn: number = 15
32
+ const shipmentColumn: number = 27
33
+ const trackingNoColumn: number = 20
34
+ let bulkContent = ''
35
+ for (let releaseGood of releaseGoods) {
36
+ const refNoLength: number = releaseGood.refNo.length
37
+ let content: string = `1710` + releaseGood.refNo
38
+ let spaceNeeded: number = orderNoColumn - refNoLength
39
+ for (let i = 0; i < spaceNeeded; i++) {
40
+ content += ` `
41
+ }
42
+ content += warehouseNumber
43
+ content += `001000001`
44
+ for (let i = 0; i < shipmentColumn; i++) {
45
+ content += ` `
46
+ }
47
+ content += `000000 `
48
+ for (let i = 0; i < trackingNoColumn; i++) {
49
+ content += ` `
50
+ }
51
+ content += `000000000000 `
52
+ const today = new Date()
53
+ const year = today.getFullYear().toString()
54
+ const month = (today.getMonth() + 1).toString().padStart(2, '0')
55
+ const day = today.getDate().toString().padStart(2, '0')
56
+ const newDate = year + month + day
57
+ content += newDate + '\n'
58
+
59
+ for (let i = 0; i < releaseGood.orderInventories.length; i++) {
60
+ const orderInventory: any = releaseGood.orderInventories[i]
61
+ let sku: string = orderInventory.product.brandSku
62
+ content += `1810` + releaseGood.refNo
63
+ for (let i = 0; i < spaceNeeded; i++) {
64
+ content += ` `
65
+ }
66
+ const idx = (i + 1).toString().padStart(2, '0')
67
+ const productCode: string = sku.substring(0, 4)
68
+ content += idx + `000000` + productCode
69
+ let productCodeSpaceNeeded = 7 - productCode.length
70
+ for (let i = 0; i < productCodeSpaceNeeded; i++) {
71
+ content += ` `
72
+ }
73
+ const orderedQty = orderInventory.releaseQty.toString().padStart(4, '0')
74
+ const pickedQty = orderInventory.pickedQty.toString().padStart(4, '0')
75
+ content += orderedQty + pickedQty + '\n'
76
+ }
77
+ bulkContent += content
78
+ }
79
+ return {
80
+ resource: { folderPath, folderType, shipmentCompletePath },
81
+ payload: { title, backupTitle, content: bulkContent, sftp, backupPath }
82
+ }
83
+ },
84
+ normalize(res) {
85
+ return res
86
+ }
87
+ }
88
+ }
@@ -0,0 +1,14 @@
1
+ export function echo() {
2
+ return {
3
+ path: '/echo',
4
+ denormalize(req) {
5
+ return { ...req }
6
+ },
7
+ normalize(res) {
8
+ return { ...res }
9
+ },
10
+ action({ store, method, path, request, platformAction }) {
11
+ return { ...request }
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,119 @@
1
+ export function getOutboundOrder() {
2
+ return {
3
+ method: 'get',
4
+ path: '{folderPath}/{folderType}/sob/data/{fileKey}',
5
+ denormalize(req) {
6
+ const { folderPath, folderType, fileKey } = req
7
+ return {
8
+ resource: { folderPath, folderType, fileKey }
9
+ }
10
+ },
11
+ normalize(res, { sftp }) {
12
+ const { Order } = res
13
+ let isAccept: boolean = false
14
+
15
+ const responseFilePattern = JSON.parse(sftp.responseFilePattern)
16
+ const acceptOrderTypes = responseFilePattern.acceptOrderTypes // ['RSO', 'MSO', 'MSBO']
17
+ const acceptNTSStatuses = responseFilePattern.acceptNTSStatuses //['', 'BACKORDER']
18
+ const acceptWarehouseNumber = responseFilePattern.acceptWarehouseNumber //['NK']
19
+ const acceptCommand = responseFilePattern.acceptCommand //['NTS', '']
20
+ const acceptFreightCode = responseFilePattern.acceptFreightCode // ['PU']
21
+
22
+ let orderHeader: any = Order.OrderHeader
23
+ let generalInfo: any = orderHeader.General
24
+ let distributorDetails: any = orderHeader.DistributorDetails
25
+ let shippingInstructions: any = Order.ShippingInstructions
26
+
27
+ let orderType: string = generalInfo.OrderType._text
28
+ let ntsStatus: string = generalInfo.NTSStatus._text || ''
29
+ let command: string = generalInfo.Command._text || ''
30
+ let warehouseNumber: string = generalInfo.WarehouseNumber._text
31
+ let freightCode: string = shippingInstructions.FreightCode._text
32
+
33
+ if (
34
+ acceptWarehouseNumber.includes(warehouseNumber) &&
35
+ acceptOrderTypes.includes(orderType) &&
36
+ acceptNTSStatuses.includes(ntsStatus) &&
37
+ acceptCommand.includes(command) &&
38
+ acceptFreightCode.includes(freightCode)
39
+ ) {
40
+ isAccept = true
41
+ }
42
+
43
+ let shippingInstruction: string = shippingInstructions.ShippingInstructions._text
44
+ let ntsDateParts = generalInfo.NTS_Date._text.split('/')
45
+ let releaseDate: string = ntsDateParts[2] + '-' + ntsDateParts[1] + '-' + ntsDateParts[0]
46
+ let orderInfo: any = {
47
+ refNo: generalInfo.OrderNumber._text,
48
+ refNo2: warehouseNumber,
49
+ type: 'b2b',
50
+ releaseDate,
51
+ collectionOrderNo: generalInfo.OrderNumber._text,
52
+ marketplaceOrderStatus: ntsStatus,
53
+ ownTransport: true,
54
+ exportOption: false,
55
+ packingOption: false,
56
+ remark: shippingInstruction,
57
+ billTo: {
58
+ billingAddress: distributorDetails.BillTo.BillToAddress._text
59
+ },
60
+ deliverTo: {
61
+ deliveryAddress1: distributorDetails.ShipTo.Address1._text,
62
+ deliveryAddress2: distributorDetails.ShipTo.Address2._text,
63
+ deliveryAddress3: distributorDetails.ShipTo.Address3._text,
64
+ attentionTo: distributorDetails.ShipTo.Name._text,
65
+ city: distributorDetails.ShipTo.City._text,
66
+ state: distributorDetails.ShipTo.ShipToState._text,
67
+ postalCode: distributorDetails.ShipTo.Zipcode._text,
68
+ country: distributorDetails.ShipTo.Country._text,
69
+ phone1: distributorDetails.ShipTo.Phone._text
70
+ }
71
+ }
72
+
73
+ let OrderLine = Order.PickList
74
+ let totalItems: number = parseFloat(Order.PickList.LinesInPickList._text)
75
+ let itemsToPick: any[] = []
76
+ let checkDuplicationValidation
77
+ if (totalItems == 1) {
78
+ if (parseFloat(OrderLine.Item.QuantityReleased._text) > 0) {
79
+ itemsToPick.push({
80
+ product: {
81
+ sku: OrderLine.Item.StockingSKU._text
82
+ },
83
+ releaseQty: parseFloat(OrderLine.Item.QuantityReleased._text)
84
+ })
85
+ }
86
+ } else {
87
+ OrderLine.Item.map(line => {
88
+ if (parseFloat(line.QuantityReleased._text) > 0) {
89
+ itemsToPick.push({
90
+ product: {
91
+ sku: line.StockingSKU._text
92
+ },
93
+ releaseQty: parseFloat(line.QuantityReleased._text)
94
+ })
95
+ }
96
+ })
97
+ }
98
+
99
+ if (ntsStatus == 'BACKORDER' && OrderLine.Item.some(itm => itm.QuantityReleased._text == 0)) {
100
+ checkDuplicationValidation = false
101
+ }
102
+
103
+ if (itemsToPick.length == 0) {
104
+ isAccept = false
105
+ }
106
+
107
+ let orderItems: any[] = itemsToPick
108
+
109
+ let result: any = {
110
+ ...orderInfo,
111
+ orderInventories: orderItems,
112
+ isAccept,
113
+ checkDuplicationValidation
114
+ }
115
+
116
+ return result
117
+ }
118
+ }
119
+ }
@@ -0,0 +1,4 @@
1
+ export * from './echo'
2
+ export * from './get-outbound-order'
3
+ export * from './create-shipment'
4
+ export * from './create-serial-number'
@@ -0,0 +1,53 @@
1
+ import '../../sftp-s3'
2
+
3
+ import { xml2js } from 'xml-js'
4
+
5
+ import { Sftp } from '../../service'
6
+ import { SFTPFILESTORAGE } from '../../sftp-const'
7
+ import { generateFiles } from '../../util/generate-files'
8
+
9
+ const debug = require('debug')('things-factory:integration-sftp:herbalife')
10
+
11
+ export type HerbalifeConfig = {}
12
+
13
+ export class Herbalife {
14
+ constructor() {}
15
+
16
+ async get(path: string, data: any = {}) {
17
+ const fileResult: any = await SFTPFILESTORAGE.readFile(path, 'utf-8')
18
+ const item: any = xml2js(fileResult, {
19
+ compact: true
20
+ })
21
+ const result: any = { ...item }
22
+ return result
23
+ }
24
+
25
+ async post(path: string, data: any = {}) {
26
+ const {
27
+ title,
28
+ backupTitle,
29
+ content,
30
+ sftp,
31
+ backupPath
32
+ }: { title: string; backupTitle: string; content: string; sftp: Sftp; backupPath: string } = data
33
+
34
+ let params: any[] = [
35
+ {
36
+ title,
37
+ uploadPath: path,
38
+ content
39
+ }
40
+ ]
41
+
42
+ if (backupPath) {
43
+ params.push({
44
+ title: backupTitle,
45
+ uploadPath: backupPath,
46
+ content
47
+ })
48
+ }
49
+ await generateFiles(params)
50
+
51
+ return sftp
52
+ }
53
+ }
@@ -0,0 +1,7 @@
1
+ import { SftpAPI } from '../sftp-api'
2
+ import * as APIS from './apis'
3
+ import { action } from './platform-action'
4
+
5
+ export * from './herbalife'
6
+
7
+ SftpAPI.registerPlatform('herbalife', action, APIS)
@@ -0,0 +1,34 @@
1
+ import { Herbalife } from './herbalife'
2
+
3
+ function substitute(path, obj) {
4
+ var props = []
5
+ var re = /{([^}]+)}/g
6
+ var text
7
+
8
+ while ((text = re.exec(path))) {
9
+ props.push(text[1])
10
+ }
11
+
12
+ var result = path
13
+ props.forEach(prop => {
14
+ let value = obj[prop.trim()]
15
+ result = result.replace(`{${prop}}`, value === undefined ? '' : value)
16
+ })
17
+
18
+ return result
19
+ }
20
+
21
+ export const action = async ({ method = 'get', path, request }) => {
22
+ const client = new Herbalife()
23
+
24
+ const { resource = {}, payload = {} } = request
25
+
26
+ path = substitute(path, resource)
27
+
28
+ var response = await client[method](path, payload)
29
+ if (response.errors) {
30
+ throw response
31
+ }
32
+
33
+ return response
34
+ }
@@ -0,0 +1,3 @@
1
+ import './herbalife'
2
+
3
+ export * from './sftp-api'
@@ -0,0 +1,43 @@
1
+ import Debug from 'debug'
2
+
3
+ import { Sftp } from '../../service'
4
+
5
+ const debug = Debug('things-factory:integration-sftp:sftp-api-decorator')
6
+
7
+ const NOOP = v => v
8
+
9
+ export const api = (target: Object, property: string, descriptor: TypedPropertyDescriptor<any>): any => {
10
+ const method = descriptor.value
11
+
12
+ descriptor.value = async function (sftp: Sftp, request) {
13
+ const SftpAPI = this
14
+
15
+ var { platform } = sftp
16
+
17
+ var { action: platformAction, apis } = SftpAPI.getPlatform(platform)
18
+
19
+ var m = apis[method.name]
20
+ if (!m) {
21
+ throw Error(`SFTP doesn't have API ${method.name}`)
22
+ }
23
+
24
+ var {
25
+ path,
26
+ method: httpMethod = 'post',
27
+ denormalize = NOOP,
28
+ normalize = NOOP,
29
+ action = platformAction
30
+ } = m.apply(this, [request])
31
+
32
+ var denormalized = await denormalize(request || {}, { sftp })
33
+ debug('request', denormalized)
34
+
35
+ var response = await action.apply(this, [{ sftp, method: httpMethod, path, request: denormalized, platformAction }])
36
+
37
+ debug('response', response)
38
+
39
+ return await normalize(response, { sftp })
40
+ }
41
+
42
+ return descriptor
43
+ }
@@ -0,0 +1,39 @@
1
+ import { getRepository } from '@things-factory/shell'
2
+
3
+ import { Sftp } from '../../service'
4
+ import { api } from './decorators'
5
+
6
+ export class SftpAPI {
7
+ static platforms = {}
8
+
9
+ static registerPlatform(name, action, apis) {
10
+ SftpAPI.platforms[name] = {
11
+ action,
12
+ apis
13
+ }
14
+ }
15
+
16
+ static getPlatform(name) {
17
+ return SftpAPI.platforms[name]
18
+ }
19
+
20
+ static async getSftp(id) {
21
+ const repository = getRepository(Sftp)
22
+ return await repository.findOne({
23
+ where: { id },
24
+ relations: ['domain']
25
+ })
26
+ }
27
+
28
+ @api
29
+ static echo(sftp, req): any {}
30
+
31
+ @api
32
+ static getOutboundOrder(sftp, req): any {}
33
+
34
+ @api
35
+ static createShipment(sftp, req): any {}
36
+
37
+ @api
38
+ static createSerialNumber(sftp, req): any {}
39
+ }
File without changes
@@ -0,0 +1,7 @@
1
+ import './routes'
2
+
3
+ export * from './middlewares'
4
+ export * from './service'
5
+ export * from './sftp-const'
6
+ export * from './controllers'
7
+ export * from './util'
@@ -0,0 +1,3 @@
1
+ export function initMiddlewares(app) {
2
+ /* can add middlewares into app */
3
+ }
@@ -0,0 +1,28 @@
1
+ const debug = require('debug')('things-factory:integration-sftp:routes')
2
+
3
+ process.on('bootstrap-module-global-public-route' as any, (app, globalPublicRouter) => {
4
+ /*
5
+ * can add global public routes to application (auth not required, tenancy not required)
6
+ *
7
+ * ex) routes.get('/path', async(context, next) => {})
8
+ * ex) routes.post('/path', async(context, next) => {})
9
+ */
10
+ })
11
+
12
+ process.on('bootstrap-module-global-private-route' as any, (app, globalPrivateRouter) => {
13
+ /*
14
+ * can add global private routes to application (auth required, tenancy not required)
15
+ */
16
+ })
17
+
18
+ process.on('bootstrap-module-domain-public-route' as any, (app, domainPublicRouter) => {
19
+ /*
20
+ * can add domain public routes to application (auth not required, tenancy required)
21
+ */
22
+ })
23
+
24
+ process.on('bootstrap-module-domain-private-route' as any, (app, domainPrivateRouter) => {
25
+ /*
26
+ * can add domain private routes to application (auth required, tenancy required)
27
+ */
28
+ })
@@ -0,0 +1,18 @@
1
+ /* EXPORT ENTITY TYPES */
2
+ export * from './sftp/sftp'
3
+
4
+ /* IMPORT ENTITIES AND RESOLVERS */
5
+ import { entities as SftpEntities, resolvers as SftpResolvers } from './sftp'
6
+
7
+ export const entities = [
8
+ /* ENTITIES */
9
+ ...SftpEntities,
10
+ ]
11
+
12
+
13
+ export const schema = {
14
+ resolverClasses: [
15
+ /* RESOLVER CLASSES */
16
+ ...SftpResolvers,
17
+ ]
18
+ }
@@ -0,0 +1,6 @@
1
+ import { Sftp } from './sftp'
2
+ import { SftpQuery } from './sftp-query'
3
+ import { SftpMutation } from './sftp-mutation'
4
+
5
+ export const entities = [Sftp]
6
+ export const resolvers = [SftpQuery, SftpMutation]