ti2-tourplan 1.0.1
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/.eslintrc.js +46 -0
- package/.github/workflows/nodejs.yml +41 -0
- package/LICENSE +674 -0
- package/README.md +40 -0
- package/__snapshots__/index.test.js.snap +216 -0
- package/index.js +131 -0
- package/index.test.js +41 -0
- package/jest.setup.js +38 -0
- package/normalizer.js +165 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Ti2's tourplan plugin
|
|
2
|
+
|
|
3
|
+
## Getting Started
|
|
4
|
+
|
|
5
|
+
### Requirements
|
|
6
|
+
|
|
7
|
+
Some environment variables are required for this plugin to run it's tests
|
|
8
|
+
|
|
9
|
+
- apiKey
|
|
10
|
+
- endpoint
|
|
11
|
+
|
|
12
|
+
## Contributing
|
|
13
|
+
|
|
14
|
+
Contributions are welcome and ecouraged.
|
|
15
|
+
|
|
16
|
+
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!
|
|
17
|
+
|
|
18
|
+
- Fork the Project
|
|
19
|
+
- Create your Feature Branch (git checkout -b feature/AmazingFeature)
|
|
20
|
+
- Commit your Changes (git commit -m 'Add some AmazingFeature')
|
|
21
|
+
- Push to the Branch (git push origin feature/AmazingFeature)
|
|
22
|
+
- Open a Pull Request
|
|
23
|
+
|
|
24
|
+
Feel free to check the [Issues Page](https://github.com/TourConnect/ti2-tourplan/issues).
|
|
25
|
+
|
|
26
|
+
## License
|
|
27
|
+
|
|
28
|
+
Distributed under the GPL-3 License. See LICENSE.txt for more information.
|
|
29
|
+
|
|
30
|
+
TL;DR Here's what the license entails:
|
|
31
|
+
|
|
32
|
+
1. Anyone can copy, modify and distribute this software.
|
|
33
|
+
2. You have to include the license and copyright notice with each and every distribution.
|
|
34
|
+
3. You can use this software privately.
|
|
35
|
+
4. You can use this software for commercial purposes.
|
|
36
|
+
5. If you dare build your business solely from this code, you risk open-sourcing the whole code base.
|
|
37
|
+
6. If you modify it, you have to indicate changes made to the code.
|
|
38
|
+
7. Any modifications of this code base MUST be distributed with the same license, GPLv3.
|
|
39
|
+
8. This software is provided without warranty.
|
|
40
|
+
9. The software author or license can not be held liable for any damages inflicted by the software.
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`search tests read allotment not empty 1`] = `
|
|
4
|
+
Object {
|
|
5
|
+
"allotment": Array [
|
|
6
|
+
Object {
|
|
7
|
+
"appliesTo": "Supplier",
|
|
8
|
+
"bySplitCode": Object {
|
|
9
|
+
"GENERAL": Object {
|
|
10
|
+
"byUnitType": Object {
|
|
11
|
+
"RM": Object {
|
|
12
|
+
"01/08/2020": Object {
|
|
13
|
+
"booked": "0",
|
|
14
|
+
"max": "0",
|
|
15
|
+
"release": "999",
|
|
16
|
+
"request": false,
|
|
17
|
+
},
|
|
18
|
+
"02/08/2020": Object {
|
|
19
|
+
"booked": "0",
|
|
20
|
+
"max": "0",
|
|
21
|
+
"release": "999",
|
|
22
|
+
"request": false,
|
|
23
|
+
},
|
|
24
|
+
"03/08/2020": Object {
|
|
25
|
+
"booked": "0",
|
|
26
|
+
"max": "0",
|
|
27
|
+
"release": "999",
|
|
28
|
+
"request": false,
|
|
29
|
+
},
|
|
30
|
+
"04/08/2020": Object {
|
|
31
|
+
"booked": "0",
|
|
32
|
+
"max": "0",
|
|
33
|
+
"release": "999",
|
|
34
|
+
"request": false,
|
|
35
|
+
},
|
|
36
|
+
"05/08/2020": Object {
|
|
37
|
+
"booked": "0",
|
|
38
|
+
"max": "0",
|
|
39
|
+
"release": "999",
|
|
40
|
+
"request": false,
|
|
41
|
+
},
|
|
42
|
+
"06/08/2020": Object {
|
|
43
|
+
"booked": "0",
|
|
44
|
+
"max": "0",
|
|
45
|
+
"release": "999",
|
|
46
|
+
"request": false,
|
|
47
|
+
},
|
|
48
|
+
"07/08/2020": Object {
|
|
49
|
+
"booked": "0",
|
|
50
|
+
"max": "0",
|
|
51
|
+
"release": "999",
|
|
52
|
+
"request": false,
|
|
53
|
+
},
|
|
54
|
+
"08/08/2020": Object {
|
|
55
|
+
"booked": "0",
|
|
56
|
+
"max": "0",
|
|
57
|
+
"release": "999",
|
|
58
|
+
"request": false,
|
|
59
|
+
},
|
|
60
|
+
"09/08/2020": Object {
|
|
61
|
+
"booked": "0",
|
|
62
|
+
"max": "0",
|
|
63
|
+
"release": "999",
|
|
64
|
+
"request": false,
|
|
65
|
+
},
|
|
66
|
+
"10/08/2020": Object {
|
|
67
|
+
"booked": "0",
|
|
68
|
+
"max": "0",
|
|
69
|
+
"release": "999",
|
|
70
|
+
"request": false,
|
|
71
|
+
},
|
|
72
|
+
"11/08/2020": Object {
|
|
73
|
+
"booked": "0",
|
|
74
|
+
"max": "0",
|
|
75
|
+
"release": "999",
|
|
76
|
+
"request": false,
|
|
77
|
+
},
|
|
78
|
+
"12/08/2020": Object {
|
|
79
|
+
"booked": "0",
|
|
80
|
+
"max": "0",
|
|
81
|
+
"release": "999",
|
|
82
|
+
"request": false,
|
|
83
|
+
},
|
|
84
|
+
"13/08/2020": Object {
|
|
85
|
+
"booked": "0",
|
|
86
|
+
"max": "0",
|
|
87
|
+
"release": "999",
|
|
88
|
+
"request": false,
|
|
89
|
+
},
|
|
90
|
+
"14/08/2020": Object {
|
|
91
|
+
"booked": "0",
|
|
92
|
+
"max": "0",
|
|
93
|
+
"release": "999",
|
|
94
|
+
"request": false,
|
|
95
|
+
},
|
|
96
|
+
"15/08/2020": Object {
|
|
97
|
+
"booked": "0",
|
|
98
|
+
"max": "0",
|
|
99
|
+
"release": "999",
|
|
100
|
+
"request": false,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
"description": "2021 Reboot Year",
|
|
107
|
+
"name": "2021 REBOOT",
|
|
108
|
+
"supplierId": "MAGLUX",
|
|
109
|
+
},
|
|
110
|
+
Object {
|
|
111
|
+
"appliesTo": "Supplier",
|
|
112
|
+
"bySplitCode": Object {
|
|
113
|
+
"GENERAL": Object {
|
|
114
|
+
"byUnitType": Object {
|
|
115
|
+
"RM": Object {
|
|
116
|
+
"01/08/2020": Object {
|
|
117
|
+
"booked": "0",
|
|
118
|
+
"max": "3",
|
|
119
|
+
"release": "14",
|
|
120
|
+
"request": true,
|
|
121
|
+
},
|
|
122
|
+
"02/08/2020": Object {
|
|
123
|
+
"booked": "0",
|
|
124
|
+
"max": "3",
|
|
125
|
+
"release": "14",
|
|
126
|
+
"request": true,
|
|
127
|
+
},
|
|
128
|
+
"03/08/2020": Object {
|
|
129
|
+
"booked": "0",
|
|
130
|
+
"max": "3",
|
|
131
|
+
"release": "14",
|
|
132
|
+
"request": true,
|
|
133
|
+
},
|
|
134
|
+
"04/08/2020": Object {
|
|
135
|
+
"booked": "0",
|
|
136
|
+
"max": "3",
|
|
137
|
+
"release": "14",
|
|
138
|
+
"request": true,
|
|
139
|
+
},
|
|
140
|
+
"05/08/2020": Object {
|
|
141
|
+
"booked": "0",
|
|
142
|
+
"max": "3",
|
|
143
|
+
"release": "14",
|
|
144
|
+
"request": true,
|
|
145
|
+
},
|
|
146
|
+
"06/08/2020": Object {
|
|
147
|
+
"booked": "0",
|
|
148
|
+
"max": "3",
|
|
149
|
+
"release": "14",
|
|
150
|
+
"request": true,
|
|
151
|
+
},
|
|
152
|
+
"07/08/2020": Object {
|
|
153
|
+
"booked": "0",
|
|
154
|
+
"max": "3",
|
|
155
|
+
"release": "14",
|
|
156
|
+
"request": true,
|
|
157
|
+
},
|
|
158
|
+
"08/08/2020": Object {
|
|
159
|
+
"booked": "0",
|
|
160
|
+
"max": "3",
|
|
161
|
+
"release": "14",
|
|
162
|
+
"request": true,
|
|
163
|
+
},
|
|
164
|
+
"09/08/2020": Object {
|
|
165
|
+
"booked": "0",
|
|
166
|
+
"max": "3",
|
|
167
|
+
"release": "14",
|
|
168
|
+
"request": true,
|
|
169
|
+
},
|
|
170
|
+
"10/08/2020": Object {
|
|
171
|
+
"booked": "0",
|
|
172
|
+
"max": "3",
|
|
173
|
+
"release": "14",
|
|
174
|
+
"request": true,
|
|
175
|
+
},
|
|
176
|
+
"11/08/2020": Object {
|
|
177
|
+
"booked": "0",
|
|
178
|
+
"max": "3",
|
|
179
|
+
"release": "14",
|
|
180
|
+
"request": true,
|
|
181
|
+
},
|
|
182
|
+
"12/08/2020": Object {
|
|
183
|
+
"booked": "0",
|
|
184
|
+
"max": "3",
|
|
185
|
+
"release": "14",
|
|
186
|
+
"request": true,
|
|
187
|
+
},
|
|
188
|
+
"13/08/2020": Object {
|
|
189
|
+
"booked": "0",
|
|
190
|
+
"max": "3",
|
|
191
|
+
"release": "14",
|
|
192
|
+
"request": true,
|
|
193
|
+
},
|
|
194
|
+
"14/08/2020": Object {
|
|
195
|
+
"booked": "0",
|
|
196
|
+
"max": "3",
|
|
197
|
+
"release": "14",
|
|
198
|
+
"request": true,
|
|
199
|
+
},
|
|
200
|
+
"15/08/2020": Object {
|
|
201
|
+
"booked": "0",
|
|
202
|
+
"max": "3",
|
|
203
|
+
"release": "14",
|
|
204
|
+
"request": true,
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
"description": "",
|
|
211
|
+
"name": "FREESALE",
|
|
212
|
+
"supplierId": "MAGLUX",
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
}
|
|
216
|
+
`;
|
package/index.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// const axios = require('axios');
|
|
2
|
+
const R = require('ramda');
|
|
3
|
+
const assert = require('assert');
|
|
4
|
+
const moment = require('moment');
|
|
5
|
+
const js2xmlparser = require('js2xmlparser');
|
|
6
|
+
const xml2js = require('xml2js');
|
|
7
|
+
const axios = require('axios');
|
|
8
|
+
|
|
9
|
+
const Normalizer = require('./normalizer');
|
|
10
|
+
|
|
11
|
+
const xmlParser = new xml2js.Parser();
|
|
12
|
+
|
|
13
|
+
const xmlOptions = {
|
|
14
|
+
prettyPrinting: {
|
|
15
|
+
enabled: false,
|
|
16
|
+
},
|
|
17
|
+
dtd: {
|
|
18
|
+
include: true,
|
|
19
|
+
name: 'tourConnect_4_00_000.dtd',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const getHeaders = ({ length }) => ({
|
|
24
|
+
Accept: 'application/xml',
|
|
25
|
+
'Content-Type': 'application/xml; charset=utf-8',
|
|
26
|
+
'Content-Length': length,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
class Plugin {
|
|
30
|
+
constructor(params) { // we get the env variables from here
|
|
31
|
+
Object.entries(params).forEach(([attr, value]) => {
|
|
32
|
+
this[attr] = value;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async queryAllotment({
|
|
37
|
+
token: {
|
|
38
|
+
endpoint = this.endpoint,
|
|
39
|
+
username = this.username,
|
|
40
|
+
password = this.password,
|
|
41
|
+
},
|
|
42
|
+
payload: {
|
|
43
|
+
dateFormat = 'DD/MM/YYYY',
|
|
44
|
+
startDate,
|
|
45
|
+
endDate,
|
|
46
|
+
keyPath,
|
|
47
|
+
},
|
|
48
|
+
}) {
|
|
49
|
+
assert(endpoint);
|
|
50
|
+
assert(startDate);
|
|
51
|
+
assert(endDate);
|
|
52
|
+
assert(keyPath, 'Must provide a supplier/product spec');
|
|
53
|
+
const keyPathArr = keyPath.split('|');
|
|
54
|
+
assert(keyPathArr.length > 1, 'Must provide a supplier id and a product id');
|
|
55
|
+
const supplierId = R.path([-2], keyPathArr);
|
|
56
|
+
const productId = R.path([-1], keyPathArr);
|
|
57
|
+
assert(supplierId);
|
|
58
|
+
assert(productId);
|
|
59
|
+
const model = {
|
|
60
|
+
GetInventoryRequest: {
|
|
61
|
+
SupplierCode: supplierId,
|
|
62
|
+
Date_From: moment(startDate, dateFormat).format('YYYY-MM-DD'),
|
|
63
|
+
Date_To: moment(endDate, dateFormat).format('YYYY-MM-DD'),
|
|
64
|
+
OptionCode: productId,
|
|
65
|
+
// AllocationName: '2021 REBOOT',
|
|
66
|
+
// Unit_Type: 'RM',
|
|
67
|
+
Login: username,
|
|
68
|
+
Password: password,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
let data = Normalizer.stripEnclosingQuotes(
|
|
72
|
+
js2xmlparser.parse('Request', model, xmlOptions),
|
|
73
|
+
);
|
|
74
|
+
data = data.replace(xmlOptions.dtd.name, `Request SYSTEM "${xmlOptions.dtd.name}"`);
|
|
75
|
+
const reply = R.path(['data'], await axios({
|
|
76
|
+
metod: 'post',
|
|
77
|
+
url: endpoint,
|
|
78
|
+
data,
|
|
79
|
+
headers: getHeaders({ length: data.length }),
|
|
80
|
+
}));
|
|
81
|
+
const returnObj = await xmlParser.parseStringPromise(reply);
|
|
82
|
+
let allotment = R.path(
|
|
83
|
+
['Reply', 'GetInventoryReply', 0, 'Allocation'],
|
|
84
|
+
returnObj,
|
|
85
|
+
);
|
|
86
|
+
// remove empty instances
|
|
87
|
+
allotment = allotment.filter(currentAllotment => Array.isArray(currentAllotment.Split));
|
|
88
|
+
allotment = allotment.map(currentAllotment => {
|
|
89
|
+
const appliesTo = R.path(['AllocationAppliesTo', 0, 'AllocationType', 0], currentAllotment);
|
|
90
|
+
const bySplitCode = {};
|
|
91
|
+
currentAllotment.Split.forEach(currentSplit => {
|
|
92
|
+
const currentSplitName = R.path(['Split_Code', 0], currentSplit);
|
|
93
|
+
const byUnitType = {};
|
|
94
|
+
R.path(['UnitTypeInventory'], currentSplit).forEach(currentUnitType => {
|
|
95
|
+
const currentUnitTypeName = R.path(['Unit_Type', 0], currentUnitType);
|
|
96
|
+
const inventoryByDay = {};
|
|
97
|
+
R.path(['PerDayInventory'], currentUnitType).forEach(dayInventory => {
|
|
98
|
+
const dayName = moment(R.path(['Date', 0], dayInventory), 'YYYY-MM-DD').format(dateFormat);
|
|
99
|
+
inventoryByDay[dayName] = {
|
|
100
|
+
release: dayInventory.Release_Period[0],
|
|
101
|
+
max: dayInventory.Max_Qty[0],
|
|
102
|
+
booked: dayInventory.Bkd_Qty[0],
|
|
103
|
+
request: dayInventory.Request_OK[0] === 'Y',
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
byUnitType[currentUnitTypeName] = {
|
|
107
|
+
...(byUnitType[currentUnitTypeName] || {}),
|
|
108
|
+
...inventoryByDay,
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
bySplitCode[currentSplitName] = {
|
|
112
|
+
...(bySplitCode[currentSplitName] || {}),
|
|
113
|
+
byUnitType,
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
return {
|
|
117
|
+
supplierId: R.path(['SupplierCode', 0], currentAllotment),
|
|
118
|
+
name: R.path(['AllocationName', 0], currentAllotment),
|
|
119
|
+
description: R.path(['AllocationDescription', 0], currentAllotment),
|
|
120
|
+
appliesTo: {
|
|
121
|
+
S: 'Supplier',
|
|
122
|
+
P: 'Product',
|
|
123
|
+
}[appliesTo] || appliesTo,
|
|
124
|
+
bySplitCode,
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
return { allotment };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
module.exports = Plugin;
|
package/index.test.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/* globals describe, it, expect */
|
|
2
|
+
const Plugin = require('./index');
|
|
3
|
+
|
|
4
|
+
const app = new Plugin({});
|
|
5
|
+
|
|
6
|
+
describe('search tests', () => {
|
|
7
|
+
const token = {
|
|
8
|
+
endpoint: process.env.ti2_tourplan_endpoint,
|
|
9
|
+
username: process.env.ti2_tourplan_username,
|
|
10
|
+
password: process.env.ti2_tourplan_password,
|
|
11
|
+
};
|
|
12
|
+
const dateFormat = 'DD/MM/YYYY';
|
|
13
|
+
describe('allotments', () => {
|
|
14
|
+
it('read allotment empty', async () => {
|
|
15
|
+
const retVal = await app.queryAllotment({
|
|
16
|
+
token,
|
|
17
|
+
payload: {
|
|
18
|
+
dateFormat,
|
|
19
|
+
startDate: '01/08/2022',
|
|
20
|
+
endDate: '15/08/2022',
|
|
21
|
+
keyPath: 'MAGLUX|SYDACMAGLUXDELXRO',
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
expect(Array.isArray(retVal.allotment)).toBeTruthy();
|
|
25
|
+
expect(retVal.allotment.length).toBe(0);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
it('read allotment not empty', async () => {
|
|
29
|
+
const retVal = await app.queryAllotment({
|
|
30
|
+
token,
|
|
31
|
+
payload: {
|
|
32
|
+
dateFormat,
|
|
33
|
+
startDate: '01/08/2020',
|
|
34
|
+
endDate: '15/08/2020',
|
|
35
|
+
keyPath: 'MAGLUX|SYDACMAGLUXDELXRO',
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
expect(Array.isArray(retVal.allotment)).toBeTruthy();
|
|
39
|
+
expect(retVal).toMatchSnapshot();
|
|
40
|
+
});
|
|
41
|
+
});
|
package/jest.setup.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* globals expect */
|
|
2
|
+
// should not require internals but is the only way to make it work on nested instances
|
|
3
|
+
const { equals } = require('expect/build/jasmineUtils');
|
|
4
|
+
const { diff: diffDefault } = require('jest-diff');
|
|
5
|
+
|
|
6
|
+
require('util').inspect.defaultOptions.depth = 8;
|
|
7
|
+
|
|
8
|
+
expect.extend({
|
|
9
|
+
toContainObject(receivedParam, argument) {
|
|
10
|
+
const received = (() => {
|
|
11
|
+
if (Array.isArray(receivedParam)) return receivedParam;
|
|
12
|
+
return [receivedParam];
|
|
13
|
+
})();
|
|
14
|
+
const pass = (() => {
|
|
15
|
+
if (Array.isArray(argument)) {
|
|
16
|
+
return equals(received,
|
|
17
|
+
expect.arrayContaining(argument.map(arg => expect.objectContaining(arg)))
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
return equals(received,
|
|
21
|
+
expect.arrayContaining([
|
|
22
|
+
expect.objectContaining(argument)
|
|
23
|
+
])
|
|
24
|
+
);
|
|
25
|
+
})();
|
|
26
|
+
if (pass) {
|
|
27
|
+
return {
|
|
28
|
+
message: () => (`expected ${this.utils.printReceived(received)} not to contain object ${this.utils.printExpected(argument)}`),
|
|
29
|
+
pass: true
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
return {
|
|
33
|
+
message: () => (diffDefault(received, argument)),
|
|
34
|
+
pass: false
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
package/normalizer.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
const _ = require('underscore');
|
|
2
|
+
|
|
3
|
+
const mapDaysToNumbers = day => {
|
|
4
|
+
switch (day) {
|
|
5
|
+
case 'Mon':
|
|
6
|
+
return 1;
|
|
7
|
+
case 'Tue':
|
|
8
|
+
return 2;
|
|
9
|
+
case 'Wed':
|
|
10
|
+
return 3;
|
|
11
|
+
case 'Thu':
|
|
12
|
+
return 4;
|
|
13
|
+
case 'Fri':
|
|
14
|
+
return 5;
|
|
15
|
+
case 'Sat':
|
|
16
|
+
return 6;
|
|
17
|
+
case 'Sun':
|
|
18
|
+
return 7;
|
|
19
|
+
default:
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const mapNumbersToDays = num => {
|
|
25
|
+
switch (num) {
|
|
26
|
+
case 1:
|
|
27
|
+
return 'Mon';
|
|
28
|
+
case 2:
|
|
29
|
+
return 'Tue';
|
|
30
|
+
case 3:
|
|
31
|
+
return 'Wed';
|
|
32
|
+
case 4:
|
|
33
|
+
return 'Thu';
|
|
34
|
+
case 5:
|
|
35
|
+
return 'Fri';
|
|
36
|
+
case 6:
|
|
37
|
+
return 'Sat';
|
|
38
|
+
case 7:
|
|
39
|
+
return 'Sun';
|
|
40
|
+
default:
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const normalizeCurrency = valueParam => {
|
|
46
|
+
const value = parseFloat(valueParam);
|
|
47
|
+
if (Number.isNaN(value)) {
|
|
48
|
+
return 'n/a';
|
|
49
|
+
}
|
|
50
|
+
return (value % 1 === 0 ? value.toFixed(4) : value.toFixed(4));
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const normalizeDate = date => {
|
|
54
|
+
if (!date) return '';
|
|
55
|
+
const d = new Date(date);
|
|
56
|
+
let month = (d.getUTCMonth() + 1).toString();
|
|
57
|
+
let day = d.getUTCDate().toString();
|
|
58
|
+
const year = d.getUTCFullYear().toString();
|
|
59
|
+
|
|
60
|
+
if (month.length < 2) month = `0${month}`;
|
|
61
|
+
if (day.length < 2) day = `0${day}`;
|
|
62
|
+
|
|
63
|
+
return [year, month, day].join('-');
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const getConsectuiveDays = days => {
|
|
67
|
+
const dayNumbers = _.map(days, mapDaysToNumbers).sort();
|
|
68
|
+
let firstDay;
|
|
69
|
+
let lastDay;
|
|
70
|
+
|
|
71
|
+
for (let i = 0; i < dayNumbers.length; i += 1) {
|
|
72
|
+
const currentDay = dayNumbers[i];
|
|
73
|
+
if (!firstDay) firstDay = currentDay;
|
|
74
|
+
lastDay = currentDay;
|
|
75
|
+
|
|
76
|
+
if ((i > 0) && (i < dayNumbers.length)) {
|
|
77
|
+
const previousDay = dayNumbers[i - 1];
|
|
78
|
+
if (currentDay - previousDay > 1) return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return `${mapNumbersToDays(firstDay)} - ${mapNumbersToDays(lastDay)}`;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
|
|
87
|
+
stripEnclosingQuotes: strParam => {
|
|
88
|
+
let str = strParam;
|
|
89
|
+
if (str && str.charAt(0) === '"') {
|
|
90
|
+
str = str.slice(1);
|
|
91
|
+
}
|
|
92
|
+
return str;
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
deepContains: (dataArray, searchItem) => {
|
|
96
|
+
for (let i = 0; i < dataArray.length; i += 1) {
|
|
97
|
+
if (_.isEqual(dataArray[i], searchItem)) return true;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
normalizeDateRange: dateRangeParam => {
|
|
103
|
+
const dateRange = {
|
|
104
|
+
fromDate: normalizeDate(dateRangeParam.start_date),
|
|
105
|
+
toDate: normalizeDate(dateRangeParam.end_date),
|
|
106
|
+
};
|
|
107
|
+
if (dateRangeParam.name) dateRange.name = dateRangeParam.name;
|
|
108
|
+
return dateRange;
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
normalizeNA: str => {
|
|
112
|
+
const na = ['N/A', 'NA', 'ON REQUEST'];
|
|
113
|
+
return str && _.indexOf(na, str.trim().toUpperCase()) === -1 ? str.trim() : null;
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
nullableTrim: str => (str ? str.trim() : str),
|
|
117
|
+
|
|
118
|
+
prettifyDays: days => {
|
|
119
|
+
let consecutiveDays;
|
|
120
|
+
if (days && days.length > 0) {
|
|
121
|
+
if (days.length === 7) return 'Everyday';
|
|
122
|
+
if (days.length === 1) return `${days[0]} Only`;
|
|
123
|
+
|
|
124
|
+
consecutiveDays = getConsectuiveDays(days);
|
|
125
|
+
return consecutiveDays || days.join(', ');
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
normalizeRateSetDetail: detail => {
|
|
131
|
+
if (detail.not_applicable) return null;
|
|
132
|
+
|
|
133
|
+
if (detail.nett_rate) {
|
|
134
|
+
return {
|
|
135
|
+
name: detail.desc,
|
|
136
|
+
adults: normalizeCurrency(detail.max_adults),
|
|
137
|
+
adultsPlusChild: normalizeCurrency(detail.max_adults_plus_child),
|
|
138
|
+
nettRate: detail.nett_rate.toFixed(2),
|
|
139
|
+
retailRate: detail.retail_rate ? detail.retail_rate.toFixed(2) : 0,
|
|
140
|
+
extraAdult: normalizeCurrency(detail.extra_adult),
|
|
141
|
+
extraChild: normalizeCurrency(detail.extra_child),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
// TODO: Other rate set types
|
|
145
|
+
return null;
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
normalizeLog: text => {
|
|
149
|
+
let logtest = text;
|
|
150
|
+
|
|
151
|
+
if (text != null && text !== undefined) {
|
|
152
|
+
if (typeof text !== 'string') {
|
|
153
|
+
logtest = JSON.stringify(logtest);
|
|
154
|
+
}
|
|
155
|
+
logtest = logtest.replace(/"userName"\s*:\s*"[^"]*"/, '"userName":"..."');
|
|
156
|
+
logtest = logtest.replace(/"password"\s*:\s*"[^"]*"/, '"password":"..."');
|
|
157
|
+
logtest = logtest.replace(
|
|
158
|
+
/<Password>[^<]*<\/Password>/,
|
|
159
|
+
'<Password>...</Password>',
|
|
160
|
+
);
|
|
161
|
+
logtest = logtest.replace(/<Login>[^<]*<\/Login>/, '<Login>...</Login>');
|
|
162
|
+
}
|
|
163
|
+
return logtest;
|
|
164
|
+
},
|
|
165
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ti2-tourplan",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Tourplan's TI2 Plugin",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "jest"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/TourConnect/ti2-tourplan.git"
|
|
12
|
+
},
|
|
13
|
+
"author": "",
|
|
14
|
+
"license": "GPLV3",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/TourConnect/ti2-tourplan/issues"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/TourConnect/ti2-tourplan#readme",
|
|
19
|
+
"jest": {
|
|
20
|
+
"testEnvironment": "node",
|
|
21
|
+
"coveragePathIgnorePatterns": [
|
|
22
|
+
"/node_modules/"
|
|
23
|
+
],
|
|
24
|
+
"setupFilesAfterEnv": [
|
|
25
|
+
"./jest.setup.js"
|
|
26
|
+
],
|
|
27
|
+
"testTimeout": 10000
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"axios": "^0.24.0",
|
|
31
|
+
"bluebird": "^3.7.2",
|
|
32
|
+
"js2xmlparser": "^4.0.2",
|
|
33
|
+
"jsonwebtoken": "^8.5.1",
|
|
34
|
+
"moment": "^2.29.1",
|
|
35
|
+
"ramda": "^0.27.1",
|
|
36
|
+
"underscore": "^1.13.4",
|
|
37
|
+
"xml2js": "^0.4.23"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"eslint": "^8.4.1",
|
|
41
|
+
"eslint-config-airbnb": "^19.0.2",
|
|
42
|
+
"eslint-plugin-import": "^2.25.4",
|
|
43
|
+
"jest": "^27.4.5",
|
|
44
|
+
"jest-cli": "^27.4.7",
|
|
45
|
+
"jest-diff": "^27.4.2"
|
|
46
|
+
}
|
|
47
|
+
}
|