-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
246 lines (246 loc) · 11.5 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
import fasterInventoryItemConstants from '@cityssm/faster-constants/inventory/items';
import { FasterReportExporter } from '@cityssm/faster-report-exporter';
import { csvReports, xlsxReports } from '@cityssm/faster-report-parser';
import { minutesToMillis } from '@cityssm/to-millis';
import Debug from 'debug';
import { DEBUG_NAMESPACE } from './debug.config.js';
import { delay, deleteFile } from './utilities.js';
const debug = Debug(`${DEBUG_NAMESPACE}:index`);
const timeoutMillis = minutesToMillis(1);
export class FasterUnofficialAPI {
#fasterReportExporter;
/**
* Initialize the Faster Unofficial API
* @param fasterTenantOrBaseUrl - The subdomain of the FASTER Web URL before ".fasterwebcloud.com"
* or the full domain and path including "/FASTER"
* @param fasterUserName - The username to log in with
* @param fasterPassword - The password to log in with
* @param options - Options
*/
constructor(fasterTenantOrBaseUrl, fasterUserName, fasterPassword, options = {}) {
this.#fasterReportExporter = new FasterReportExporter(fasterTenantOrBaseUrl, fasterUserName, fasterPassword, options);
}
/**
* Retrieves a list of assets using the W114 report.
* @returns A list of assets
*/
async getAssets() {
debug('Exporting asset list...');
const assetReportPath = await this.#fasterReportExporter.exportAssetList('Excel');
debug(`Asset list exported: ${assetReportPath}`);
debug('Parsing asset report...');
const report = xlsxReports.parseW114ExcelReport(assetReportPath);
debug(`Asset report parsed with ${report.data.length} asset(s).`);
await deleteFile(assetReportPath);
return report.data;
}
/**
* Retrieves a list of inventory items using the W200 report.
* @returns A list of inventory items, grouped by storeroom
*/
async getInventory() {
debug('Exporting inventory report...');
const inventoryReportPath = await this.#fasterReportExporter.exportInventory('Excel');
debug(`Inventory report exported: ${inventoryReportPath}`);
debug('Parsing inventory report...');
const report = xlsxReports.parseW200ExcelReport(inventoryReportPath);
debug(`Inventory report parsed with ${report.data.length} storeroom(s).`);
await deleteFile(inventoryReportPath);
return report.data;
}
/**
* Updates an inventory item, truncating fields where necessary.
* @see {@link https://github.com/cityssm/node-faster-constants/blob/main/inventory/items.ts|@cityssm/faster-constants - Inventory Item Constants} for truncate lengths.
* @param itemNumber - The item number of the inventory item to update.
* @param storeroom - The storeroom of the inventory item to update.
* @param fieldsToUpdate - The fields to update.
* @param fieldsToUpdate.itemName - The updated item name.
* @param fieldsToUpdate.itemDescription - The updated item description.
* @param fieldsToUpdate.binLocation - The updated bin location.
* @param fieldsToUpdate.alternateLocation - The updated alternate location.
* @returns `true` if the inventory item was updated, `false` if not.
*/
async updateInventoryItem(itemNumber, storeroom, fieldsToUpdate) {
if (Object.keys(fieldsToUpdate).length === 0) {
debug('No fields to update.');
return false;
}
if ((fieldsToUpdate.itemName ?? '').length >
fasterInventoryItemConstants.itemName.maxLength) {
debug(`Item name exceeds maximum length of ${fasterInventoryItemConstants.itemName.maxLength} and will be truncated.`);
}
if ((fieldsToUpdate.binLocation ?? '').length >
fasterInventoryItemConstants.binLocation.maxLength) {
debug(`Bin location exceeds maximum length of ${fasterInventoryItemConstants.binLocation.maxLength} and will be truncated.`);
}
if ((fieldsToUpdate.alternateLocation ?? '').length >
fasterInventoryItemConstants.alternateLocation.maxLength) {
debug(`Alternate location exceeds maximum length of ${fasterInventoryItemConstants.alternateLocation.maxLength} and will be truncated.`);
}
const { browser, page } = await this.#fasterReportExporter._getLoggedInFasterPage();
try {
await page.goto(this.#fasterReportExporter.fasterUrlBuilder.inventorySearchUrl(itemNumber, true), {
timeout: timeoutMillis
});
await page.waitForNetworkIdle({
timeout: timeoutMillis
});
// No results or more than one result.
// Not supported
if (page
.url()
.startsWith(this.#fasterReportExporter.fasterUrlBuilder.inventorySearchUrl())) {
debug('Either no results or multiple results found. Not supported.');
return false;
}
/*
* Verify the storeroom
*/
const headingText = (await page.$eval('#ctl00_ContentPlaceHolder_Content_DetailMenu_PartHeaderLabel', (heading) => heading.textContent)) ?? '';
if (!headingText.trim().endsWith(`[${storeroom}]`)) {
debug('Item not found in the specified storeroom.');
return false;
}
/*
* Get the item id
*/
const pageUrlString = page.url();
const pageUrl = new URL(pageUrlString);
const itemId = pageUrl.searchParams.get('id') ?? '';
if (itemId === '') {
debug('Item ID not found.');
return false;
}
/*
* Open the item identification update form
*/
const formUrl = `${this.#fasterReportExporter.fasterUrlBuilder.baseUrl}/Domains/Parts/PartDetail/IdentificationEdit.aspx?id=${itemId}`;
await page.goto(formUrl, {
timeout: timeoutMillis
});
await page.waitForNetworkIdle({
timeout: timeoutMillis
});
/*
* Update the fields
*/
if (fieldsToUpdate.itemName !== undefined) {
await page.$eval(`#${fasterInventoryItemConstants.itemName.inputId}`, (itemNameTextBox, itemName) => {
itemNameTextBox.value = itemName;
}, fieldsToUpdate.itemName.slice(0, fasterInventoryItemConstants.itemName.maxLength));
}
if (fieldsToUpdate.itemDescription !== undefined) {
await page.$eval('#PartDescriptionRadTextBox', (itemDescriptionTextBox, itemDescription) => {
itemDescriptionTextBox.value = itemDescription;
}, fieldsToUpdate.itemDescription);
}
if (fieldsToUpdate.binLocation !== undefined) {
await page.$eval(`#${fasterInventoryItemConstants.binLocation.inputId}`, (binLocationTextBox, binLocation) => {
binLocationTextBox.value = binLocation;
}, fieldsToUpdate.binLocation.slice(0, fasterInventoryItemConstants.binLocation.maxLength));
}
if (fieldsToUpdate.alternateLocation !== undefined) {
await page.$eval(`#${fasterInventoryItemConstants.alternateLocation.inputId}`, (alternateLocationTextBox, alternateLocation) => {
alternateLocationTextBox.value = alternateLocation;
}, fieldsToUpdate.alternateLocation.slice(0, fasterInventoryItemConstants.alternateLocation.maxLength));
}
/*
* Save the form
*/
await page.$eval('#SaveTopButton', (saveButton) => {
saveButton.click();
});
await delay();
await page.waitForNetworkIdle({
timeout: timeoutMillis
});
return true;
}
finally {
try {
await browser.close();
}
catch {
// Ignore errors
}
}
}
/**
* Retrieves the message log using the W603 report.
* @param startDate - The start date of the message log to retrieve.
* @param endDate - The end date of the message log to retrieve. Defaults to `startDate`.
* @returns The message log.
*/
async getMessageLog(startDate, endDate) {
debug('Exporting message log...');
const messageLogPath = await this.#fasterReportExporter.exportMessageLogger(startDate, endDate ?? startDate, 'CSV');
debug(`Message log exported: ${messageLogPath}`);
debug('Parsing message log...');
const report = await csvReports.parseFasterCsvReport(messageLogPath, csvReports.fasterCsvReportOptions.w603);
debug(`Message log parsed with ${report.data.length} message(s).`);
await deleteFile(messageLogPath);
return report.data;
}
/**
* Executes an integration by name.
* @param integrationName - The name of the integration to execute.
* @returns `true` if the integration was executed, `false` if not.
*/
async executeIntegration(integrationName) {
const { browser, page } = await this.#fasterReportExporter._getLoggedInFasterPage();
try {
await page.goto(this.#fasterReportExporter.fasterUrlBuilder.integrationsUrl, {
timeout: timeoutMillis
});
await page.waitForNetworkIdle({
timeout: timeoutMillis
});
// Find the integration row
const integrationTableRowElements = await page.$$(
// eslint-disable-next-line no-secrets/no-secrets
'#ctl00_ContentPlaceHolder_Content_IntegrationRadDock_C_IntegrationRadGrid_ctl00 tbody tr');
for (const integrationTableRowElement of integrationTableRowElements) {
const integrationNameElement = await integrationTableRowElement.$('td:nth-child(1) a');
if (integrationNameElement === null) {
continue;
}
const integrationNameText = await integrationNameElement.evaluate((cell) => cell.textContent);
if (integrationNameText === integrationName) {
debug(`Integration found: ${integrationName}`);
const integrationActionLinkElements = await integrationTableRowElement.$$('td:nth-child(3) a');
for (const integrationActionLinkElement of integrationActionLinkElements) {
const integrationActionLinkText = await integrationActionLinkElement.evaluate((cell) => cell.textContent);
if (integrationActionLinkText === 'Execute') {
debug(`Executing integration: ${integrationName}`);
await integrationActionLinkElement.click();
await delay();
await page.waitForNetworkIdle({
timeout: timeoutMillis
});
return true;
}
}
}
}
}
finally {
try {
await browser.close();
}
catch {
// Ignore errors
}
}
return false;
}
}
export const integrationNames = {
inventoryImportUtility: 'Inventory Import Utility'
};
export const parser = {
csvReports,
xlsxReports
};
export const exporter = {
FasterReportExporter
};