Compare commits
50 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
daf9f197eb | ||
|
|
5848daef72 | ||
|
|
c655badae2 | ||
|
|
7af70f7512 | ||
|
|
a8dcc542cc | ||
|
|
a16c680d04 | ||
|
|
c6d083ce02 | ||
|
|
ec30e73b3e | ||
|
|
92b05a290e | ||
|
|
0d9a7dda53 | ||
|
|
601eed6db8 | ||
|
|
9b708d5e8e | ||
|
|
6b4bc27205 | ||
|
|
0eb0e335fe | ||
|
|
cd1e8b0b15 | ||
|
|
f7799ffd03 | ||
|
|
57baa3d9fd | ||
|
|
f796dd0f89 | ||
|
|
517c30787d | ||
|
|
c8771275ce | ||
|
|
de26979e44 | ||
|
|
661678eb1c | ||
|
|
1728982b2b | ||
|
|
8aa747edc5 | ||
|
|
544494ce0d | ||
|
|
2e25324ae9 | ||
|
|
f525ec6fb8 | ||
|
|
8296d914c5 | ||
|
|
3d691568ff | ||
|
|
7ab070e2bc | ||
|
|
47a974e3cb | ||
|
|
eac81cdffb | ||
|
|
77c3e6f7e7 | ||
|
|
7f07da4360 | ||
|
|
c47144df72 | ||
|
|
163eaac110 | ||
|
|
19ce1c66ad | ||
|
|
e2b4b408ed | ||
|
|
567171c722 | ||
|
|
03acb78ab2 | ||
|
|
e7c4797fef | ||
|
|
88c35e8c48 | ||
|
|
8623172aa1 | ||
|
|
6ecc67184d | ||
|
|
09973ecb3b | ||
|
|
db9e86e4c8 | ||
|
|
85c446bc57 | ||
|
|
5cf6f47bdc | ||
|
|
1db4cbeeb8 | ||
|
|
c88bf4065e |
346
Fortellis Notes.md
Normal file
346
Fortellis Notes.md
Normal file
@@ -0,0 +1,346 @@
|
||||
Fortellis Notes
|
||||
|
||||
Subscription ID
|
||||
|
||||
- Appears to give us a list of all dealerships we have access to, and `apiDmsInfo` contains the integrations that are enabled for that dealership.
|
||||
- Will likely need to filter based on the DMS ID or something?
|
||||
- Should store the whole subscription object. Contains department information needed in subsequent calls.
|
||||
|
||||
Department ID
|
||||
|
||||
- May have multiple departments. Appears that financial stuff goes to Accounting, History will go to Service.
|
||||
- TODO: How do we handle the multiple departments that may come up.
|
||||
|
||||
###Internal Questions
|
||||
|
||||
* Overview of the redis storing mechanism to cache this data.
|
||||
*
|
||||
|
||||
# GL Wip Posting
|
||||
|
||||
## Org Helper Return Data
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"acctgLgnID": "DEVWB-A",
|
||||
"applCode": "V",
|
||||
"coID": "77",
|
||||
"companyName": "TEST SYS C187092 DEVWB",
|
||||
"lgnDesc": "DEV WRITE BACK VMS",
|
||||
"logon": "DEVWB-V"
|
||||
},
|
||||
{
|
||||
"acctgLgnID": "DEVWB-A",
|
||||
"applCode": "F",
|
||||
"coID": "77",
|
||||
"companyName": "TEST SYS C187092 DEVWB",
|
||||
"lgnDesc": "DEV WRITE BACK F&I SALES",
|
||||
"logon": "DEVWB-FI"
|
||||
},
|
||||
{
|
||||
"acctgLgnID": "DEVWB-A",
|
||||
"applCode": "CS",
|
||||
"coID": "77",
|
||||
"companyName": "TEST SYS C187092 DEVWB",
|
||||
"lgnDesc": "DEV WRITE BACK SERVICE",
|
||||
"logon": "DEVWB-S"
|
||||
},
|
||||
{
|
||||
"acctgLgnID": "DEVWB-A",
|
||||
"applCode": "A",
|
||||
"coID": "77",
|
||||
"companyName": "TEST SYS C187092 DEVWB",
|
||||
"lgnDesc": "DEV WRITE BACK ACCTG",
|
||||
"logon": "DEVWB-A"
|
||||
},
|
||||
{
|
||||
"acctgLgnID": "DEVWB-A",
|
||||
"applCode": "SL",
|
||||
"coID": "77",
|
||||
"companyName": "TEST SYS C187092 DEVWB",
|
||||
"lgnDesc": "DEV WRTIE BACK SLS MGMT",
|
||||
"logon": "DEVWB-SL"
|
||||
},
|
||||
{
|
||||
"acctgLgnID": "DEVWB-A",
|
||||
"applCode": "O",
|
||||
"coID": "77",
|
||||
"companyName": "TEST SYS C187092 DEVWB",
|
||||
"lgnDesc": "DEV WRITE BACK PARTS",
|
||||
"logon": "DEVWB-I"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Journal Helper Return Data
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "32",
|
||||
"jrnlName": "PARTS SALES",
|
||||
"jrnlType": "S",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "4",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "92",
|
||||
"jrnlName": "YTD ADJUSTMENTS",
|
||||
"jrnlType": "Y",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "3",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "12",
|
||||
"jrnlName": "FLEET SALES",
|
||||
"jrnlType": "S",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "9",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "57",
|
||||
"jrnlName": "CASH RECEIPTS (OPEN-ITEM)",
|
||||
"jrnlType": "R",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "1",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "93",
|
||||
"jrnlName": "SET UP HISTORY",
|
||||
"jrnlType": "H",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "10",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "88",
|
||||
"jrnlName": "F/S STATISCAL DATA",
|
||||
"jrnlType": "F",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "10",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "58",
|
||||
"jrnlName": "WARRANTY CREDITS",
|
||||
"jrnlType": "G",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "3",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "FC",
|
||||
"jrnlName": "FINANCE CHARGE",
|
||||
"jrnlType": "A",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "12",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "94",
|
||||
"jrnlName": "SET UP SCHEDULES",
|
||||
"jrnlType": "C",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "3",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "95",
|
||||
"jrnlName": "SET UP GENERAL LEDGER",
|
||||
"jrnlType": "B",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "3",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "20",
|
||||
"jrnlName": "USED VEHICLE SALES",
|
||||
"jrnlType": "S",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "9",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "60",
|
||||
"jrnlName": "CASH DISBURSEMENTS",
|
||||
"jrnlType": "G",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "2",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "30",
|
||||
"jrnlName": "SERVICE SALES",
|
||||
"jrnlType": "S",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "7",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "40",
|
||||
"jrnlName": "PAYROLL",
|
||||
"jrnlType": "G",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "11",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "15",
|
||||
"jrnlName": "DEALER TRADES",
|
||||
"jrnlType": "S",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "9",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "70",
|
||||
"jrnlName": "NEW VEHICLE PURCHASES",
|
||||
"jrnlType": "G",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "8",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "25",
|
||||
"jrnlName": "USED WHOLESALE",
|
||||
"jrnlType": "S",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "9",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "75",
|
||||
"jrnlName": "GENERAL PURCHASES",
|
||||
"jrnlType": "G",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "5",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "10",
|
||||
"jrnlName": "NEW VEHICLE SALES",
|
||||
"jrnlType": "S",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "9",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "80",
|
||||
"jrnlName": "GENERAL JOURNAL",
|
||||
"jrnlType": "G",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "3",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "11",
|
||||
"jrnlName": "WORK IN PROGRESS",
|
||||
"jrnlType": "G",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "10",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "56",
|
||||
"jrnlName": "CASH RECEIPTS (BALANCE FWD)",
|
||||
"jrnlType": "G",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "1",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "81",
|
||||
"jrnlName": "STANDARD ENTRIES",
|
||||
"jrnlType": "G",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "6",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "51",
|
||||
"jrnlName": "CASH RECEIPTS JOURNAL - EFT",
|
||||
"jrnlType": "G",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "10",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "61",
|
||||
"jrnlName": "CASH DISBURSMENTS -EFT",
|
||||
"jrnlType": "G",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "10",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
},
|
||||
{
|
||||
"companyNo": "77",
|
||||
"jrnlID": "71",
|
||||
"jrnlName": "USED VEHICLE PURCHASES",
|
||||
"jrnlType": "G",
|
||||
"intercoFlag": "0",
|
||||
"defaultDocType": "8",
|
||||
"errCode": "",
|
||||
"errMsg": ""
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
# Feedback
|
||||
|
||||
- Receiving bad request errors, with no details. API errors page doesn't indicate what's wrong for certain types of error codes.
|
||||
- API Error page works on a several minute delay.
|
||||
661
client/package-lock.json
generated
661
client/package-lock.json
generated
@@ -2620,431 +2620,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz",
|
||||
"integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz",
|
||||
"integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz",
|
||||
"integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz",
|
||||
"integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz",
|
||||
"integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz",
|
||||
"integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz",
|
||||
"integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz",
|
||||
"integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz",
|
||||
"integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz",
|
||||
"integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz",
|
||||
"integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz",
|
||||
"integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz",
|
||||
"integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz",
|
||||
"integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz",
|
||||
"integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz",
|
||||
"integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz",
|
||||
"integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz",
|
||||
"integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz",
|
||||
"integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz",
|
||||
"integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz",
|
||||
"integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz",
|
||||
"integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz",
|
||||
"integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz",
|
||||
"integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz",
|
||||
"integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz",
|
||||
@@ -3776,166 +3351,6 @@
|
||||
"@parcel/watcher-win32-x64": "2.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-android-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-freebsd-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
|
||||
@@ -3976,46 +3391,6 @@
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-ia32": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
|
||||
"integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
|
||||
@@ -9260,9 +8635,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -13132,6 +12507,21 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright/node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/possible-typed-array-names": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
||||
@@ -17443,21 +16833,6 @@
|
||||
"sourcemap-codec": "^1.4.8"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/postcss": {
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||
|
||||
@@ -138,7 +138,7 @@ export function App({
|
||||
);
|
||||
}
|
||||
|
||||
if (!isPartsEntry && currentEula && !currentUser.eulaIsAccepted) {
|
||||
if (currentEula && !currentUser.eulaIsAccepted) {
|
||||
return <Eula />;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
|
||||
import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
|
||||
import { handleUpload } from "../documents-upload/documents-upload.utility";
|
||||
import { handleUpload as handleUploadToImageProxy } from "../documents-upload-imgproxy/documents-upload-imgproxy.utility";
|
||||
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
billEnterModal: selectBillEnterModal,
|
||||
@@ -451,9 +450,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
||||
setEnterAgain(false);
|
||||
}}
|
||||
>
|
||||
<RbacWrapper action="bills:enter">
|
||||
<BillFormContainer form={form} disableInvNumber={billEnterModal.context.disableInvNumber} />
|
||||
</RbacWrapper>
|
||||
<BillFormContainer form={form} disableInvNumber={billEnterModal.context.disableInvNumber} />
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -142,37 +142,17 @@ export default function JobLifecycleDashboardComponent({ data, bodyshop, ...card
|
||||
title={t("job_lifecycle.content.legend_title")}
|
||||
style={{ marginTop: "10px" }}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: 8
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
{lifecycleData.summations.map((key) => (
|
||||
<Tag
|
||||
key={key.status}
|
||||
color={key.color}
|
||||
style={{
|
||||
// IMPORTANT: let the tag grow with its content
|
||||
width: "auto",
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
boxSizing: "border-box"
|
||||
}}
|
||||
>
|
||||
<Tag key={key.status} color={key.color} style={{ width: "13vh", padding: "4px", margin: "4px" }}>
|
||||
<div
|
||||
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||
style={{
|
||||
backgroundColor: "var(--tag-wrapper-bg)",
|
||||
color: "var(--tag-wrapper-text)",
|
||||
padding: "4px 8px",
|
||||
textAlign: "center",
|
||||
whiteSpace: "nowrap" // keep it on one line while letting the pill expand
|
||||
padding: "4px",
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
{key.status} [{lifecycleData.statusCounts[key.status]}] ({key.roundedPercentage})
|
||||
|
||||
@@ -7,6 +7,8 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import Dinero from "dinero.js";
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -22,13 +24,28 @@ export default connect(mapStateToProps, mapDispatchToProps)(DmsAllocationsSummar
|
||||
export function DmsAllocationsSummary({ socket, bodyshop, jobId, title }) {
|
||||
const { t } = useTranslation();
|
||||
const [allocationsSummary, setAllocationsSummary] = useState([]);
|
||||
const {
|
||||
treatments: { Fortellis }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Fortellis"],
|
||||
splitKey: bodyshop.imexshopid
|
||||
});
|
||||
const { socket: wsssocket } = useSocket();
|
||||
|
||||
useEffect(() => {
|
||||
if (socket.connected) {
|
||||
socket.emit("cdk-calculate-allocations", jobId, (ack) => {
|
||||
if (Fortellis.treatment === "on") {
|
||||
wsssocket.emit("fortellis-calculate-allocations", jobId, (ack) => {
|
||||
setAllocationsSummary(ack);
|
||||
socket.allocationsSummary = ack;
|
||||
});
|
||||
} else {
|
||||
if (socket.connected) {
|
||||
socket.emit("cdk-calculate-allocations", jobId, (ack) => {
|
||||
setAllocationsSummary(ack);
|
||||
socket.allocationsSummary = ack;
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [socket, socket.connected, jobId]);
|
||||
|
||||
@@ -76,8 +93,12 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId, title }) {
|
||||
extra={
|
||||
<Button
|
||||
onClick={() => {
|
||||
socket.emit("cdk-calculate-allocations", jobId, (ack) => setAllocationsSummary(ack));
|
||||
}}
|
||||
if (Fortellis.treatment === "on") {
|
||||
socket.emit("fortellis-calculate-allocations", jobId, (ack) => setAllocationsSummary(ack));
|
||||
} else {
|
||||
socket.emit("cdk-calculate-allocations", jobId, (ack) => setAllocationsSummary(ack));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
@@ -18,15 +19,26 @@ export default connect(mapStateToProps, mapDispatchToProps)(DmsCdkMakesRefetch);
|
||||
export function DmsCdkMakesRefetch({ currentUser, bodyshop }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
treatments: { Fortellis }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Fortellis"],
|
||||
splitKey: bodyshop.imexshopid
|
||||
});
|
||||
|
||||
if (!currentUser.email.includes("@imex.")) return null;
|
||||
|
||||
const handleRefetch = async () => {
|
||||
setLoading(true);
|
||||
await axios.post("/cdk/getvehicles", {
|
||||
cdk_dealerid: bodyshop.cdk_dealerid,
|
||||
bodyshopid: bodyshop.id
|
||||
});
|
||||
try {
|
||||
setLoading(true);
|
||||
await axios.post(`cdk${Fortellis.treatment === "on" ? "/fortellis" : ""}/getvehicles`, {
|
||||
cdk_dealerid: bodyshop.cdk_dealerid,
|
||||
bodyshopid: bodyshop.id
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Button, Checkbox, Col, Table } from "antd";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket";
|
||||
import { socket } from "../../pages/dms/dms.container";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
@@ -15,42 +17,125 @@ const mapDispatchToProps = () => ({
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DmsCustomerSelector);
|
||||
|
||||
export function DmsCustomerSelector({ bodyshop }) {
|
||||
export function DmsCustomerSelector({ bodyshop, jobid }) {
|
||||
const { t } = useTranslation();
|
||||
const [customerList, setcustomerList] = useState([]);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [selectedCustomer, setSelectedCustomer] = useState(null);
|
||||
const [dmsType, setDmsType] = useState("cdk");
|
||||
|
||||
socket.on("cdk-select-customer", (customerList) => {
|
||||
setOpen(true);
|
||||
setDmsType("cdk");
|
||||
setcustomerList(customerList);
|
||||
});
|
||||
socket.on("pbs-select-customer", (customerList) => {
|
||||
setOpen(true);
|
||||
setDmsType("pbs");
|
||||
setcustomerList(customerList);
|
||||
const {
|
||||
treatments: { Fortellis }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Fortellis"],
|
||||
splitKey: bodyshop.imexshopid
|
||||
});
|
||||
|
||||
const { socket: wsssocket } = useSocket();
|
||||
|
||||
useEffect(() => {
|
||||
if (Fortellis.treatment === "on") {
|
||||
const handleFortellisSelectCustomer = (customerList) => {
|
||||
setOpen(true);
|
||||
setDmsType("cdk");
|
||||
setcustomerList(customerList);
|
||||
};
|
||||
wsssocket.on("fortellis-select-customer", handleFortellisSelectCustomer);
|
||||
return () => {
|
||||
wsssocket.off("fortellis-select-customer", handleFortellisSelectCustomer);
|
||||
};
|
||||
} else {
|
||||
const handleCdkSelectCustomer = (customerList) => {
|
||||
setOpen(true);
|
||||
setDmsType("cdk");
|
||||
setcustomerList(customerList);
|
||||
};
|
||||
const handlePbsSelectCustomer = (customerList) => {
|
||||
setOpen(true);
|
||||
setDmsType("pbs");
|
||||
setcustomerList(customerList);
|
||||
};
|
||||
socket.on("cdk-select-customer", handleCdkSelectCustomer);
|
||||
socket.on("pbs-select-customer", handlePbsSelectCustomer);
|
||||
return () => {
|
||||
socket.off("cdk-select-customer", handleCdkSelectCustomer);
|
||||
socket.off("pbs-select-customer", handlePbsSelectCustomer);
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onUseSelected = () => {
|
||||
setOpen(false);
|
||||
socket.emit(`${dmsType}-selected-customer`, selectedCustomer);
|
||||
if (Fortellis.treatment === "on") {
|
||||
wsssocket.emit(`fortellis-selected-customer`, { selectedCustomerId: selectedCustomer, jobid });
|
||||
} else {
|
||||
socket.emit(`${dmsType}-selected-customer`, selectedCustomer);
|
||||
}
|
||||
setSelectedCustomer(null);
|
||||
};
|
||||
|
||||
const onUseGeneric = () => {
|
||||
setOpen(false);
|
||||
socket.emit(`${dmsType}-selected-customer`, bodyshop.cdk_configuration.generic_customer_number);
|
||||
|
||||
if (Fortellis.treatment === "on") {
|
||||
wsssocket.emit(`fortellis-selected-customer`, {
|
||||
selectedCustomerId: bodyshop.cdk_configuration.generic_customer_number,
|
||||
jobid
|
||||
});
|
||||
} else {
|
||||
socket.emit(`${dmsType}-selected-customer`, bodyshop.cdk_configuration.generic_customer_number);
|
||||
}
|
||||
setSelectedCustomer(null);
|
||||
};
|
||||
|
||||
const onCreateNew = () => {
|
||||
setOpen(false);
|
||||
socket.emit(`${dmsType}-selected-customer`, null);
|
||||
|
||||
if (Fortellis.treatment === "on") {
|
||||
wsssocket.emit(`fortellis-selected-customer`, { selectedCustomerId: null, jobid });
|
||||
} else {
|
||||
socket.emit(`${dmsType}-selected-customer`, null);
|
||||
}
|
||||
setSelectedCustomer(null);
|
||||
};
|
||||
|
||||
const fortellisColumns = [
|
||||
{
|
||||
title: t("jobs.fields.dms.id"),
|
||||
dataIndex: "customerId",
|
||||
key: "id"
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.vinowner"),
|
||||
dataIndex: "vinOwner",
|
||||
key: "vinOwner",
|
||||
render: (text, record) => <Checkbox disabled checked={record.vinOwner} />
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.name1"),
|
||||
dataIndex: ["customerName", "firstName"],
|
||||
key: "firstName",
|
||||
sorter: (a, b) => alphaSort(a.customerName?.firstName, b.customerName?.firstName)
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.name1"),
|
||||
dataIndex: ["customerName", "lastName"],
|
||||
key: "lastName",
|
||||
sorter: (a, b) => alphaSort(a.customerName?.lastName, b.customerName?.lastName)
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.address"),
|
||||
|
||||
key: "address",
|
||||
render: (record) =>
|
||||
`${record.postalAddress?.addressLine1} ${record.postalAddress?.addressLine2 ? `, ${record.postalAddress?.addressLine2}` : ""},
|
||||
${record.postalAddress?.city} ${record.postalAddress?.state} ${record.postalAddress?.postalCode} ${
|
||||
record.postalAddress?.country
|
||||
}`
|
||||
}
|
||||
];
|
||||
|
||||
const cdkColumns = [
|
||||
{
|
||||
title: t("jobs.fields.dms.id"),
|
||||
@@ -117,13 +202,13 @@ export function DmsCustomerSelector({ bodyshop }) {
|
||||
</div>
|
||||
)}
|
||||
pagination={{ position: "top" }}
|
||||
columns={dmsType === "cdk" ? cdkColumns : pbsColumns}
|
||||
rowKey={(record) => (dmsType === "cdk" ? record.id.value : record.ContactId)}
|
||||
columns={dmsType === "cdk" ? (Fortellis.treatment === "on" ? fortellisColumns : cdkColumns) : pbsColumns}
|
||||
rowKey={(record) => (dmsType === "cdk" ? record.id?.value || record.customerId : record.ContactId)}
|
||||
dataSource={customerList}
|
||||
//onChange={handleTableChange}
|
||||
rowSelection={{
|
||||
onSelect: (record) => {
|
||||
setSelectedCustomer(dmsType === "cdk" ? record.id.value : record.ContactId);
|
||||
setSelectedCustomer(dmsType === "cdk" ? record.id?.value || record.customerId : record.ContactId);
|
||||
},
|
||||
type: "radio",
|
||||
selectedRowKeys: [selectedCustomer]
|
||||
|
||||
@@ -26,6 +26,8 @@ import DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.component
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -36,8 +38,17 @@ const mapDispatchToProps = () => ({
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DmsPostForm);
|
||||
|
||||
export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
|
||||
const {
|
||||
treatments: { Fortellis }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Fortellis"],
|
||||
splitKey: bodyshop.imexshopid
|
||||
});
|
||||
|
||||
const [form] = Form.useForm();
|
||||
const { t } = useTranslation();
|
||||
const { socket: wsssocket } = useSocket();
|
||||
|
||||
const handlePayerSelect = (value, index) => {
|
||||
form.setFieldsValue({
|
||||
@@ -58,13 +69,22 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
|
||||
};
|
||||
|
||||
const handleFinish = (values) => {
|
||||
socket.emit(`${determineDmsType(bodyshop)}-export-job`, {
|
||||
jobid: job.id,
|
||||
txEnvelope: values
|
||||
});
|
||||
console.log(logsRef);
|
||||
if (Fortellis.treatment === "on") {
|
||||
wsssocket.emit("fortellis-export-job", {
|
||||
jobid: job.id,
|
||||
txEnvelope: {
|
||||
...values,
|
||||
SubscriptionID: bodyshop.cdk_dealerid
|
||||
}
|
||||
});
|
||||
} else {
|
||||
socket.emit(`${determineDmsType(bodyshop)}-export-job`, {
|
||||
jobid: job.id,
|
||||
txEnvelope: values
|
||||
});
|
||||
}
|
||||
|
||||
if (logsRef) {
|
||||
console.log("executing", logsRef);
|
||||
logsRef.curent &&
|
||||
logsRef.current.scrollIntoView({
|
||||
behavior: "smooth"
|
||||
@@ -169,7 +189,7 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<Space>
|
||||
<DmsCdkMakes form={form} socket={socket} job={job} />
|
||||
<DmsCdkMakes form={form} job={job} />
|
||||
<DmsCdkMakesRefetch />
|
||||
<Form.Item name="dms_unsold" label={t("jobs.fields.dms.dms_unsold")} initialValue={false}>
|
||||
<Switch />
|
||||
|
||||
@@ -55,8 +55,7 @@ const Eula = ({ currentEula, currentUser, acceptEula }) => {
|
||||
const useremail = currentUser.email;
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { accepted_terms, ...otherFormValues } = formValues;
|
||||
const { ...otherFormValues } = formValues;
|
||||
|
||||
// Trim the values of the fields before submitting
|
||||
const trimmedFormValues = Object.entries(otherFormValues).reduce((acc, [key, value]) => {
|
||||
|
||||
@@ -222,37 +222,17 @@ export function JobLifecycleComponent({ bodyshop, job, statuses }) {
|
||||
</div>
|
||||
</BlurWrapperComponent>
|
||||
<Card type="inner" title={t("job_lifecycle.content.legend_title")} style={{ marginTop: "10px" }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: 8
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
{lifecycleData.durations.summations.map((key) => (
|
||||
<Tag
|
||||
key={key.status}
|
||||
color={key.color}
|
||||
style={{
|
||||
// let the tag grow with its content
|
||||
width: "auto",
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
boxSizing: "border-box"
|
||||
}}
|
||||
>
|
||||
<Tag key={key.status} color={key.color} style={{ width: "13vh", padding: "4px", margin: "4px" }}>
|
||||
<div
|
||||
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||
style={{
|
||||
backgroundColor: "var(--tag-wrapper-bg)",
|
||||
color: "var(--tag-wrapper-text)",
|
||||
padding: "4px 8px",
|
||||
textAlign: "center",
|
||||
whiteSpace: "nowrap" // single line; tag gets wider instead of text escaping
|
||||
padding: "4px",
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
{key.status} (
|
||||
|
||||
@@ -35,14 +35,16 @@ export function JobsDocumentsImgproxyDownloadButton({ galleryImages, identifier,
|
||||
...galleryImages.other.filter((image) => image.isSelected)
|
||||
];
|
||||
|
||||
const downloadProgress = ({ loaded }) => {
|
||||
setDownload((currentDownloadState) => ({
|
||||
downloaded: loaded ?? 0,
|
||||
speed: (loaded ?? 0) - (currentDownloadState?.downloaded ?? 0)
|
||||
}));
|
||||
};
|
||||
function downloadProgress(progressEvent) {
|
||||
setDownload((currentDownloadState) => {
|
||||
return {
|
||||
downloaded: progressEvent.loaded || 0,
|
||||
speed: (progressEvent.loaded || 0) - ((currentDownloadState && currentDownloadState.downloaded) || 0)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const standardMediaDownload = (bufferData) => {
|
||||
function standardMediaDownload(bufferData) {
|
||||
try {
|
||||
const a = document.createElement("a");
|
||||
const url = window.URL.createObjectURL(new Blob([bufferData]));
|
||||
@@ -53,26 +55,29 @@ export function JobsDocumentsImgproxyDownloadButton({ galleryImages, identifier,
|
||||
setLoading(false);
|
||||
setDownload(null);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleDownload = async () => {
|
||||
logImEXEvent("jobs_documents_download");
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data } = await axios({
|
||||
const response = await axios({
|
||||
url: "/media/imgproxy/download",
|
||||
method: "POST",
|
||||
responseType: "blob",
|
||||
data: { jobId, documentids: imagesToDownload.map((_) => _.id) },
|
||||
onDownloadProgress: downloadProgress
|
||||
});
|
||||
// Use the response data (Blob) to trigger download
|
||||
standardMediaDownload(data);
|
||||
} catch {
|
||||
// handle error (optional)
|
||||
} finally {
|
||||
|
||||
setLoading(false);
|
||||
setDownload(null);
|
||||
|
||||
// Use the response data (Blob) to trigger download
|
||||
standardMediaDownload(response.data);
|
||||
} catch {
|
||||
setLoading(false);
|
||||
setDownload(null);
|
||||
// handle error (optional)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -76,14 +76,14 @@ function JobsDocumentsImgproxyComponent({
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
<JobsDocumentsGallerySelectAllComponent galleryImages={galleryImages} setGalleryImages={setGalleryImages} />
|
||||
{!billId && (
|
||||
<JobsDocumentsGalleryReassign galleryImages={galleryImages} callback={fetchThumbnails || refetch} />
|
||||
)}
|
||||
<JobsDocumentsDownloadButton galleryImages={galleryImages} identifier={downloadIdentifier} jobId={jobId} />
|
||||
<JobsDocumentsDeleteButton
|
||||
galleryImages={galleryImages}
|
||||
deletionCallback={billsCallback || fetchThumbnails || refetch}
|
||||
/>
|
||||
{!billId && (
|
||||
<JobsDocumentsGalleryReassign galleryImages={galleryImages} callback={fetchThumbnails || refetch} />
|
||||
)}
|
||||
</Space>
|
||||
</Col>
|
||||
{!hasMediaAccess && (
|
||||
|
||||
@@ -67,7 +67,7 @@ export default function JobsDocumentsImgproxyDeleteButton({ galleryImages, delet
|
||||
okButtonProps={{ danger: true }}
|
||||
cancelText={t("general.actions.cancel")}
|
||||
>
|
||||
<Button danger disabled={imagesToDelete.length < 1} loading={loading}>
|
||||
<Button disabled={imagesToDelete.length < 1} loading={loading}>
|
||||
{t("documents.actions.delete")}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
|
||||
@@ -107,8 +107,8 @@ export function JobsDocumentsLocalGallery({
|
||||
<a href={CreateExplorerLinkForJob({ jobid: job.id })}>
|
||||
<Button>{t("documents.labels.openinexplorer")}</Button>
|
||||
</a>
|
||||
<JobsDocumentsLocalGallerySelectAllComponent jobid={job.id} />
|
||||
<JobsDocumentsLocalGalleryReassign jobid={job.id} />
|
||||
<JobsDocumentsLocalGallerySelectAllComponent jobid={job.id} />
|
||||
<JobsLocalGalleryDownloadButton job={job} />
|
||||
<JobsDocumentsLocalDeleteButton jobid={job.id} />
|
||||
</Space>
|
||||
|
||||
@@ -28,8 +28,6 @@ export function JobsDocumentsLocalDeleteButton({ bodyshop, getJobMedia, allMedia
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const imagesToDelete = (allMedia?.[jobid] || []).filter((i) => i.isSelected);
|
||||
|
||||
const handleDelete = async () => {
|
||||
logImEXEvent("job_documents_delete");
|
||||
setLoading(true);
|
||||
@@ -38,7 +36,7 @@ export function JobsDocumentsLocalDeleteButton({ bodyshop, getJobMedia, allMedia
|
||||
`${bodyshop.localmediaserverhttp}/jobs/delete`,
|
||||
{
|
||||
jobid: jobid,
|
||||
files: imagesToDelete.map((i) => i.filename)
|
||||
files: (allMedia?.[jobid] || []).filter((i) => i.isSelected).map((i) => i.filename)
|
||||
},
|
||||
{ headers: { ims_token: bodyshop.localmediatoken } }
|
||||
);
|
||||
@@ -62,17 +60,14 @@ export function JobsDocumentsLocalDeleteButton({ bodyshop, getJobMedia, allMedia
|
||||
|
||||
return (
|
||||
<Popconfirm
|
||||
disabled={imagesToDelete.length < 1}
|
||||
icon={<QuestionCircleOutlined style={{ color: "red" }} />}
|
||||
onConfirm={handleDelete}
|
||||
title={t("documents.labels.confirmdelete")}
|
||||
okText={t("general.actions.delete")}
|
||||
okButtonProps={{ danger: true }}
|
||||
okButtonProps={{ type: "danger" }}
|
||||
cancelText={t("general.actions.cancel")}
|
||||
>
|
||||
<Button danger disabled={imagesToDelete.length < 1} loading={loading}>
|
||||
{t("documents.actions.delete")}
|
||||
</Button>
|
||||
<Button loading={loading}>{t("documents.actions.delete")}</Button>
|
||||
</Popconfirm>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Button, Space } from "antd";
|
||||
import { Button } from "antd";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import cleanAxios from "../../utils/CleanAxios";
|
||||
import formatBytes from "../../utils/formatbytes";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectAllMedia } from "../../redux/media/media.selectors";
|
||||
@@ -19,63 +19,45 @@ export default connect(mapStateToProps, mapDispatchToProps)(JobsLocalGalleryDown
|
||||
|
||||
export function JobsLocalGalleryDownloadButton({ bodyshop, allMedia, job }) {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [download, setDownload] = useState(false);
|
||||
const [download, setDownload] = useState(null);
|
||||
|
||||
const imagesToDownload = (allMedia?.[job.id] || []).filter((i) => i.isSelected);
|
||||
|
||||
const downloadProgress = ({ loaded }) => {
|
||||
setDownload((currentDownloadState) => ({
|
||||
downloaded: loaded || 0,
|
||||
speed: (loaded || 0) - (currentDownloadState?.downloaded || 0)
|
||||
}));
|
||||
};
|
||||
|
||||
const standardMediaDownload = (bufferData, filename) => {
|
||||
try {
|
||||
const a = document.createElement("a");
|
||||
const url = window.URL.createObjectURL(new Blob([bufferData]));
|
||||
a.href = url;
|
||||
a.download = `${filename}.zip`;
|
||||
a.click();
|
||||
} catch {
|
||||
setLoading(false);
|
||||
setDownload(null);
|
||||
}
|
||||
};
|
||||
function downloadProgress(progressEvent) {
|
||||
setDownload((currentDownloadState) => {
|
||||
return {
|
||||
downloaded: progressEvent.loaded || 0,
|
||||
speed: (progressEvent.loaded || 0) - (currentDownloadState?.downloaded || 0)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const handleDownload = async () => {
|
||||
const { localmediaserverhttp, localmediatoken } = bodyshop;
|
||||
const { id, ro_number } = job;
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await cleanAxios.post(
|
||||
`${localmediaserverhttp}/jobs/download`,
|
||||
{
|
||||
jobid: id,
|
||||
files: imagesToDownload.map((i) => i.filename)
|
||||
},
|
||||
{
|
||||
headers: { ims_token: localmediatoken },
|
||||
responseType: "arraybuffer",
|
||||
onDownloadProgress: downloadProgress
|
||||
}
|
||||
);
|
||||
standardMediaDownload(response.data, ro_number);
|
||||
} catch {
|
||||
// handle error (optional)
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setDownload(null);
|
||||
}
|
||||
const theDownloadedZip = await cleanAxios.post(
|
||||
`${bodyshop.localmediaserverhttp}/jobs/download`,
|
||||
{
|
||||
jobid: job.id,
|
||||
files: (allMedia?.[job.id] || []).filter((i) => i.isSelected).map((i) => i.filename)
|
||||
},
|
||||
{
|
||||
headers: { ims_token: bodyshop.localmediatoken },
|
||||
responseType: "arraybuffer",
|
||||
onDownloadProgress: downloadProgress
|
||||
}
|
||||
);
|
||||
setDownload(null);
|
||||
standardMediaDownload(theDownloadedZip.data, job.ro_number);
|
||||
};
|
||||
|
||||
return (
|
||||
<Button disabled={imagesToDownload < 1} loading={download || loading} onClick={handleDownload}>
|
||||
<Space>
|
||||
<span>{t("documents.actions.download")}</span>
|
||||
{download && <span>{`(${formatBytes(download.downloaded)} @ ${formatBytes(download.speed)} / second)`}</span>}
|
||||
</Space>
|
||||
<Button loading={!!download} onClick={handleDownload}>
|
||||
{t("documents.actions.download")}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function standardMediaDownload(bufferData, filename) {
|
||||
const a = document.createElement("a");
|
||||
const url = window.URL.createObjectURL(new Blob([bufferData]));
|
||||
a.href = url;
|
||||
a.download = `${filename}.zip`;
|
||||
a.click();
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ export default function ShopInfoNotificationsAutoadd({ bodyshop }) {
|
||||
<Text type="secondary">{t("bodyshop.labels.notifications.followers")}</Text>
|
||||
{employeeOptions.length > 0 ? (
|
||||
<Form.Item
|
||||
normalize={(value) => (value || []).filter((id) => typeof id === "string" && id.trim() !== "")}
|
||||
name="notification_followers"
|
||||
rules={[
|
||||
{
|
||||
@@ -43,6 +42,11 @@ export default function ShopInfoNotificationsAutoadd({ bodyshop }) {
|
||||
options={employeeOptions}
|
||||
placeholder={t("bodyshop.fields.notifications.placeholder")}
|
||||
showEmail={true}
|
||||
onChange={(value) => {
|
||||
// Filter out null or invalid values before passing to Form
|
||||
const cleanedValue = value?.filter((id) => id != null && typeof id === "string" && id.trim() !== "");
|
||||
return cleanedValue;
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
) : (
|
||||
|
||||
@@ -4,18 +4,10 @@ import { useTranslation } from "react-i18next";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
export default function ShopInfoSpeedPrint() {
|
||||
const { t } = useTranslation();
|
||||
const allTemplates = TemplateList("job");
|
||||
const TemplateListGenerated = InstanceRenderManager({
|
||||
imex: Object.fromEntries(
|
||||
Object.entries(allTemplates).filter(([, { enhanced_payroll }]) => !enhanced_payroll)
|
||||
),
|
||||
rome: allTemplates
|
||||
});
|
||||
|
||||
const TemplateListGenerated = TemplateList("job");
|
||||
return (
|
||||
<Form.List name={["speedprint"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
|
||||
@@ -16,7 +16,7 @@ const mapDispatchToProps = () => ({
|
||||
export function TechHeader({ technician }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Header style={{ textAlign: "center", height: "auto", overflow: "visible" }}>
|
||||
<Header style={{ textAlign: "center" }}>
|
||||
<Typography.Title style={{ color: "#fff" }}>
|
||||
{technician
|
||||
? t("tech.labels.loggedin", {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { Button, Card, Form, InputNumber, Popover, Select, Space } from "antd";
|
||||
import { Button, Card, Col, Form, InputNumber, Popover, Row, Select } from "antd";
|
||||
import axios from "axios";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -124,97 +124,71 @@ export function TechClockOffButton({
|
||||
cost_center: isShiftTicket ? "timetickets.labels.shift" : technician ? technician.cost_center : null
|
||||
}}
|
||||
>
|
||||
<Space direction="vertical">
|
||||
{!isShiftTicket ? (
|
||||
<div>
|
||||
<Form.Item
|
||||
label={t("timetickets.fields.actualhrs")}
|
||||
name="actualhrs"
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} precision={1} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("timetickets.fields.productivehrs")}
|
||||
name="productivehrs"
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
({ getFieldValue }) => ({
|
||||
validator(rule, value) {
|
||||
if (!bodyshop.tt_enforce_hours_for_tech_console) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (!value || getFieldValue("cost_center") === null || !lineTicketData)
|
||||
return Promise.resolve();
|
||||
//Check the cost center,
|
||||
const totals = CalculateAllocationsTotals(
|
||||
bodyshop,
|
||||
lineTicketData.joblines,
|
||||
lineTicketData.timetickets,
|
||||
lineTicketData.jobs_by_pk.lbr_adjustments
|
||||
);
|
||||
const fieldTypeToCheck =
|
||||
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber ? "mod_lbr_ty" : "cost_center";
|
||||
const costCenterDiff =
|
||||
Math.round(
|
||||
totals.find((total) => total[fieldTypeToCheck] === getFieldValue("cost_center"))
|
||||
?.difference * 10
|
||||
) / 10;
|
||||
if (value > costCenterDiff)
|
||||
return Promise.reject(t("timetickets.validation.hoursenteredmorethanavailable"));
|
||||
else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={!isShiftTicket ? 8 : 24}>
|
||||
{!isShiftTicket ? (
|
||||
<div>
|
||||
<Form.Item
|
||||
label={t("timetickets.fields.actualhrs")}
|
||||
name="actualhrs"
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
})
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} precision={1} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
) : null}
|
||||
<Form.Item
|
||||
name="cost_center"
|
||||
label={t("timetickets.fields.cost_center")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Select disabled={isShiftTicket}>
|
||||
{isShiftTicket ? (
|
||||
<Select.Option value="timetickets.labels.shift">{t("timetickets.labels.shift")}</Select.Option>
|
||||
) : (
|
||||
emps &&
|
||||
emps.rates.map((item) => (
|
||||
<Select.Option key={item.cost_center}>
|
||||
{item.cost_center === "timetickets.labels.shift"
|
||||
? t(item.cost_center)
|
||||
: bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
||||
? t(`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`)
|
||||
: item.cost_center}
|
||||
</Select.Option>
|
||||
))
|
||||
)}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
{isShiftTicket ? (
|
||||
<div></div>
|
||||
) : (
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} precision={1} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("timetickets.fields.productivehrs")}
|
||||
name="productivehrs"
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
({ getFieldValue }) => ({
|
||||
validator(rule, value) {
|
||||
if (!bodyshop.tt_enforce_hours_for_tech_console) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (!value || getFieldValue("cost_center") === null || !lineTicketData)
|
||||
return Promise.resolve();
|
||||
|
||||
//Check the cost center,
|
||||
const totals = CalculateAllocationsTotals(
|
||||
bodyshop,
|
||||
lineTicketData.joblines,
|
||||
lineTicketData.timetickets,
|
||||
lineTicketData.jobs_by_pk.lbr_adjustments
|
||||
);
|
||||
|
||||
const fieldTypeToCheck =
|
||||
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber ? "mod_lbr_ty" : "cost_center";
|
||||
|
||||
const costCenterDiff =
|
||||
Math.round(
|
||||
totals.find((total) => total[fieldTypeToCheck] === getFieldValue("cost_center"))
|
||||
?.difference * 10
|
||||
) / 10;
|
||||
|
||||
if (value > costCenterDiff)
|
||||
return Promise.reject(t("timetickets.validation.hoursenteredmorethanavailable"));
|
||||
else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
})
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} precision={1} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
) : null}
|
||||
<Form.Item
|
||||
name="status"
|
||||
label={t("jobs.fields.status")}
|
||||
initialValue={lineTicketData && lineTicketData.jobs_by_pk.status}
|
||||
name="cost_center"
|
||||
label={t("timetickets.fields.cost_center")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
@@ -222,32 +196,67 @@ export function TechClockOffButton({
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Select>
|
||||
{bodyshop.md_ro_statuses.production_statuses.map((item) => (
|
||||
<Select.Option key={item}></Select.Option>
|
||||
))}
|
||||
<Select disabled={isShiftTicket}>
|
||||
{isShiftTicket ? (
|
||||
<Select.Option value="timetickets.labels.shift">{t("timetickets.labels.shift")}</Select.Option>
|
||||
) : (
|
||||
emps &&
|
||||
emps.rates.map((item) => (
|
||||
<Select.Option key={item.cost_center}>
|
||||
{item.cost_center === "timetickets.labels.shift"
|
||||
? t(item.cost_center)
|
||||
: bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
||||
? t(`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`)
|
||||
: item.cost_center}
|
||||
</Select.Option>
|
||||
))
|
||||
)}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
)}
|
||||
<Button type="primary" htmlType="submit" loading={loading}>
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
<TechJobClockoutDelete completedCallback={completedCallback} timeTicketId={timeTicketId} />
|
||||
|
||||
{isShiftTicket ? (
|
||||
<div></div>
|
||||
) : (
|
||||
<Form.Item
|
||||
name="status"
|
||||
label={t("jobs.fields.status")}
|
||||
initialValue={lineTicketData && lineTicketData.jobs_by_pk.status}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Select>
|
||||
{bodyshop.md_ro_statuses.production_statuses.map((item) => (
|
||||
<Select.Option key={item}></Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
)}
|
||||
<Button type="primary" htmlType="submit" loading={loading}>
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
<TechJobClockoutDelete completedCallback={completedCallback} timeTicketId={timeTicketId} />
|
||||
</Col>
|
||||
{!isShiftTicket && (
|
||||
<LaborAllocationContainer jobid={jobId || null} loading={queryLoading} lineTicketData={lineTicketData} />
|
||||
<Col span={16}>
|
||||
<LaborAllocationContainer
|
||||
jobid={jobId || null}
|
||||
loading={queryLoading}
|
||||
lineTicketData={lineTicketData}
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
</Space>
|
||||
</Row>
|
||||
</Form>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
content={<div style={{ maxHeight: "75vh", overflowY: "auto" }}>{overlay}</div>}
|
||||
trigger="click"
|
||||
getPopupContainer={() => document.querySelector('#time-ticket-modal')}
|
||||
>
|
||||
<Popover content={overlay} trigger="click">
|
||||
<Button loading={loading} {...otherBtnProps}>
|
||||
{t("timetickets.actions.clockout")}
|
||||
</Button>
|
||||
|
||||
@@ -21,6 +21,8 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -54,6 +56,14 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const { jobId } = search;
|
||||
const notification = useNotification();
|
||||
const {
|
||||
treatments: { Fortellis }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Fortellis"],
|
||||
splitKey: bodyshop.imexshopid
|
||||
});
|
||||
const { socket: wsssocket } = useSocket();
|
||||
|
||||
const { loading, error, data } = useQuery(QUERY_JOB_EXPORT_DMS, {
|
||||
variables: { id: jobId },
|
||||
@@ -84,45 +94,75 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
}, [t, setBreadcrumbs, setSelectedHeader]);
|
||||
|
||||
useEffect(() => {
|
||||
socket.on("connect", () => socket.emit("set-log-level", logLevel));
|
||||
socket.on("reconnect", () => {
|
||||
setLogs((logs) => {
|
||||
return [
|
||||
...logs,
|
||||
{
|
||||
timestamp: new Date(),
|
||||
level: "warn",
|
||||
message: "Reconnected to CDK Export Service"
|
||||
}
|
||||
];
|
||||
});
|
||||
});
|
||||
socket.on("connect_error", (err) => {
|
||||
console.log(`connect_error due to ${err}`, err);
|
||||
notification.error({ message: err.message });
|
||||
});
|
||||
socket.on("log-event", (payload) => {
|
||||
setLogs((logs) => {
|
||||
return [...logs, payload];
|
||||
});
|
||||
});
|
||||
socket.on("export-success", (payload) => {
|
||||
notification.success({
|
||||
message: t("jobs.successes.exported")
|
||||
});
|
||||
insertAuditTrail({
|
||||
jobid: payload,
|
||||
operation: AuditTrailMapping.jobexported(),
|
||||
type: "jobexported"
|
||||
});
|
||||
history("/manage/accounting/receivables");
|
||||
});
|
||||
if (Fortellis.treatment === "on") {
|
||||
wsssocket.emit("set-log-level", logLevel);
|
||||
|
||||
if (socket.disconnected) socket.connect();
|
||||
return () => {
|
||||
socket.removeAllListeners();
|
||||
socket.disconnect();
|
||||
};
|
||||
const handleLogEvent = (payload) => {
|
||||
setLogs((logs) => {
|
||||
return [...logs, payload];
|
||||
});
|
||||
};
|
||||
|
||||
const handleExportSuccess = (payload) => {
|
||||
notification.success({
|
||||
message: t("jobs.successes.exported")
|
||||
});
|
||||
insertAuditTrail({
|
||||
jobid: payload,
|
||||
operation: AuditTrailMapping.jobexported(),
|
||||
type: "jobexported"
|
||||
});
|
||||
history("/manage/accounting/receivables");
|
||||
};
|
||||
|
||||
wsssocket.on("fortellis-log-event", handleLogEvent);
|
||||
wsssocket.on("export-success", handleExportSuccess);
|
||||
|
||||
return () => {
|
||||
wsssocket.off("fortellis-log-event", handleLogEvent);
|
||||
wsssocket.off("export-success", handleExportSuccess);
|
||||
};
|
||||
} else {
|
||||
socket.on("connect", () => socket.emit("set-log-level", logLevel));
|
||||
socket.on("reconnect", () => {
|
||||
setLogs((logs) => {
|
||||
return [
|
||||
...logs,
|
||||
{
|
||||
timestamp: new Date(),
|
||||
level: "warn",
|
||||
message: "Reconnected to CDK Export Service"
|
||||
}
|
||||
];
|
||||
});
|
||||
});
|
||||
socket.on("connect_error", (err) => {
|
||||
console.log(`connect_error due to ${err}`, err);
|
||||
notification.error({ message: err.message });
|
||||
});
|
||||
socket.on("log-event", (payload) => {
|
||||
setLogs((logs) => {
|
||||
return [...logs, payload];
|
||||
});
|
||||
});
|
||||
socket.on("export-success", (payload) => {
|
||||
notification.success({
|
||||
message: t("jobs.successes.exported")
|
||||
});
|
||||
insertAuditTrail({
|
||||
jobid: payload,
|
||||
operation: AuditTrailMapping.jobexported(),
|
||||
type: "jobexported"
|
||||
});
|
||||
history("/manage/accounting/receivables");
|
||||
});
|
||||
|
||||
if (socket.disconnected) socket.connect();
|
||||
return () => {
|
||||
socket.removeAllListeners();
|
||||
socket.disconnect();
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (loading) return <LoadingSpinner />;
|
||||
@@ -136,6 +176,9 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
|
||||
return (
|
||||
<div>
|
||||
{Fortellis.treatment === "on" && (
|
||||
<AlertComponent message="Posting to Fortellis" type="warning" showIcon closable />
|
||||
)}
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col md={24} lg={10}>
|
||||
<DmsAllocationsSummary
|
||||
@@ -157,7 +200,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
<DmsPostForm socket={socket} jobId={jobId} job={data && data.jobs_by_pk} logsRef={logsRef} />
|
||||
</Col>
|
||||
|
||||
<DmsCustomerSelector />
|
||||
<DmsCustomerSelector jobid={jobId} />
|
||||
|
||||
<Col span={24}>
|
||||
<div ref={logsRef}>
|
||||
@@ -182,8 +225,13 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
<Button
|
||||
onClick={() => {
|
||||
setLogs([]);
|
||||
socket.disconnect();
|
||||
socket.connect();
|
||||
if (Fortellis.treatment === "on") {
|
||||
wsssocket.disconnect();
|
||||
wsssocket.connect();
|
||||
} else {
|
||||
socket.disconnect();
|
||||
socket.connect();
|
||||
}
|
||||
}}
|
||||
>
|
||||
Reconnect
|
||||
|
||||
@@ -50,7 +50,7 @@ import {
|
||||
} from "./user.actions";
|
||||
import UserActionTypes from "./user.types";
|
||||
//import * as amplitude from '@amplitude/analytics-browser';
|
||||
import posthog from "posthog-js";
|
||||
import posthog from 'posthog-js';
|
||||
|
||||
const fpPromise = FingerprintJS.load();
|
||||
|
||||
@@ -269,11 +269,11 @@ export function* signInSuccessSaga({ payload }) {
|
||||
instanceSeg,
|
||||
...(isParts
|
||||
? [
|
||||
InstanceRenderManager({
|
||||
imex: "ImexPartsManagement",
|
||||
rome: "RomePartsManagement"
|
||||
})
|
||||
]
|
||||
InstanceRenderManager({
|
||||
imex: "ImexPartsManagement",
|
||||
rome: "RomePartsManagement"
|
||||
})
|
||||
]
|
||||
: [])
|
||||
];
|
||||
window.$crisp.push(["set", "session:segments", [segs]]);
|
||||
@@ -375,31 +375,17 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
||||
const isParts = yield select((state) => state.application.isPartsEntry === true);
|
||||
const instanceSeg = InstanceRenderManager({ imex: "imex", rome: "rome" });
|
||||
|
||||
const featureSegments =
|
||||
payload.features?.allAccess === true
|
||||
? ["allAccess"]
|
||||
: [
|
||||
"basic",
|
||||
...Object.keys(payload.features).filter(
|
||||
(key) =>
|
||||
payload.features[key] === true ||
|
||||
(typeof payload.features[key] === "string" && !isNaN(Date.parse(payload.features[key])))
|
||||
)
|
||||
];
|
||||
|
||||
const additionalSegments = [
|
||||
payload.cdk_dealerid && "CDK",
|
||||
payload.pbs_serialnumber && "PBS",
|
||||
// payload.rr_dealerid && "Reynolds",
|
||||
payload.accountingconfig.qbo === true && "QBO",
|
||||
payload.accountingconfig.qbo === false &&
|
||||
!payload.cdk_dealerid &&
|
||||
!payload.pbs_serialnumber &&
|
||||
// !payload.rr_dealerid &&
|
||||
"QBD"
|
||||
].filter(Boolean);
|
||||
|
||||
featureSegments.push(...additionalSegments);
|
||||
let featureSegments;
|
||||
if (payload.features?.allAccess === true) {
|
||||
featureSegments = ["allAccess"];
|
||||
} else {
|
||||
const featureKeys = Object.keys(payload.features).filter(
|
||||
(key) =>
|
||||
payload.features[key] === true ||
|
||||
(typeof payload.features[key] === "string" && !isNaN(Date.parse(payload.features[key])))
|
||||
);
|
||||
featureSegments = ["basic", ...featureKeys];
|
||||
}
|
||||
|
||||
const regionSeg = payload.region_config ? `region:${payload.region_config}` : null;
|
||||
const segments = [instanceSeg, ...(regionSeg ? [regionSeg] : []), ...featureSegments];
|
||||
|
||||
@@ -3209,7 +3209,6 @@
|
||||
"parts_not_recieved_vendor": "Parts Not Received by Vendor",
|
||||
"parts_received_not_scheduled": "Parts Received for Jobs Not Scheduled",
|
||||
"payments_by_date": "Payments by Date",
|
||||
"payments_by_date_excel": "Payments by Date - Excel",
|
||||
"payments_by_date_payment": "Payments by Date and Payment Type",
|
||||
"payments_by_date_type": "Payments by Date and Customer Type",
|
||||
"production_by_category": "Production by Category",
|
||||
|
||||
@@ -3209,7 +3209,6 @@
|
||||
"parts_not_recieved_vendor": "",
|
||||
"parts_received_not_scheduled": "",
|
||||
"payments_by_date": "",
|
||||
"payments_by_date_excel": "",
|
||||
"payments_by_date_payment": "",
|
||||
"payments_by_date_type": "",
|
||||
"production_by_category": "",
|
||||
|
||||
@@ -3209,7 +3209,6 @@
|
||||
"parts_not_recieved_vendor": "",
|
||||
"parts_received_not_scheduled": "",
|
||||
"payments_by_date": "",
|
||||
"payments_by_date_excel": "",
|
||||
"payments_by_date_payment": "",
|
||||
"payments_by_date_type": "",
|
||||
"production_by_category": "",
|
||||
|
||||
@@ -1218,18 +1218,6 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
group: "customers"
|
||||
},
|
||||
payments_by_date_excel: {
|
||||
title: i18n.t("reportcenter.templates.payments_by_date_excel"),
|
||||
subject: i18n.t("reportcenter.templates.payments_by_date_excel"),
|
||||
key: "payments_by_date_excel",
|
||||
reporttype: "excel",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.payments"),
|
||||
field: i18n.t("payments.fields.date")
|
||||
},
|
||||
group: "customers"
|
||||
},
|
||||
schedule: {
|
||||
title: i18n.t("reportcenter.templates.schedule"),
|
||||
subject: i18n.t("reportcenter.templates.schedule"),
|
||||
|
||||
@@ -1156,11 +1156,7 @@
|
||||
enable_manual: false
|
||||
update:
|
||||
columns:
|
||||
- imexshopid
|
||||
- timezone
|
||||
- shopname
|
||||
- notification_followers
|
||||
- state
|
||||
- md_order_statuses
|
||||
retry_conf:
|
||||
interval_sec: 10
|
||||
@@ -3702,7 +3698,6 @@
|
||||
- deliverchecklist
|
||||
- depreciation_taxes
|
||||
- dms_allocation
|
||||
- dms_id
|
||||
- driveable
|
||||
- employee_body
|
||||
- employee_csr
|
||||
@@ -3980,7 +3975,6 @@
|
||||
- deliverchecklist
|
||||
- depreciation_taxes
|
||||
- dms_allocation
|
||||
- dms_id
|
||||
- driveable
|
||||
- employee_body
|
||||
- employee_csr
|
||||
@@ -4270,7 +4264,6 @@
|
||||
- deliverchecklist
|
||||
- depreciation_taxes
|
||||
- dms_allocation
|
||||
- dms_id
|
||||
- driveable
|
||||
- employee_body
|
||||
- employee_csr
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
alter table "public"."media_analytics_detail" drop constraint "media_analytics_detail_jobid_fkey",
|
||||
add constraint "media_analytics_detail_jobid_fkey"
|
||||
foreign key ("jobid")
|
||||
references "public"."jobs"
|
||||
("id") on update restrict on delete restrict;
|
||||
@@ -1,5 +0,0 @@
|
||||
alter table "public"."media_analytics_detail" drop constraint "media_analytics_detail_jobid_fkey",
|
||||
add constraint "media_analytics_detail_jobid_fkey"
|
||||
foreign key ("jobid")
|
||||
references "public"."jobs"
|
||||
("id") on update set null on delete set null;
|
||||
@@ -1,23 +0,0 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- CREATE OR REPLACE FUNCTION set_fk_to_null_if_invalid_media_analytics()
|
||||
-- RETURNS TRIGGER AS $$
|
||||
-- BEGIN
|
||||
-- -- Check if the foreign key value is not NULL
|
||||
-- IF NEW.jobid IS NOT NULL THEN
|
||||
-- -- Check if the corresponding record exists in the parent table
|
||||
-- IF NOT EXISTS (SELECT 1 FROM jobs WHERE id = NEW.jobid) THEN
|
||||
-- -- If it doesn't exist, set the foreign key to NULL
|
||||
-- NEW.jobid = NULL;
|
||||
-- END IF;
|
||||
-- END IF;
|
||||
--
|
||||
-- -- Return the (potentially modified) record to be inserted/updated
|
||||
-- RETURN NEW;
|
||||
-- END;
|
||||
-- $$ LANGUAGE plpgsql;
|
||||
--
|
||||
-- CREATE TRIGGER media_analytics_fk_null
|
||||
-- BEFORE INSERT OR UPDATE ON media_analytics_detail
|
||||
-- FOR EACH ROW
|
||||
-- EXECUTE FUNCTION set_fk_to_null_if_invalid_media_analytics();
|
||||
@@ -1,21 +0,0 @@
|
||||
CREATE OR REPLACE FUNCTION set_fk_to_null_if_invalid_media_analytics()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
-- Check if the foreign key value is not NULL
|
||||
IF NEW.jobid IS NOT NULL THEN
|
||||
-- Check if the corresponding record exists in the parent table
|
||||
IF NOT EXISTS (SELECT 1 FROM jobs WHERE id = NEW.jobid) THEN
|
||||
-- If it doesn't exist, set the foreign key to NULL
|
||||
NEW.jobid = NULL;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- Return the (potentially modified) record to be inserted/updated
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER media_analytics_fk_null
|
||||
BEFORE INSERT OR UPDATE ON media_analytics_detail
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION set_fk_to_null_if_invalid_media_analytics();
|
||||
@@ -1 +0,0 @@
|
||||
DROP INDEX IF EXISTS "public"."media_analytics_detail_bodyshopid";
|
||||
@@ -1,2 +0,0 @@
|
||||
CREATE INDEX "media_analytics_detail_bodyshopid" on
|
||||
"public"."media_analytics_detail" using btree ("bodyshopid");
|
||||
@@ -1 +0,0 @@
|
||||
DROP INDEX IF EXISTS "public"."media_analytics_detail_jobid";
|
||||
@@ -1,2 +0,0 @@
|
||||
CREATE INDEX "media_analytics_detail_jobid" on
|
||||
"public"."media_analytics_detail" using btree ("jobid");
|
||||
@@ -1 +0,0 @@
|
||||
DROP INDEX IF EXISTS "public"."media_analytics_detail_media_analytics";
|
||||
@@ -1,2 +0,0 @@
|
||||
CREATE INDEX "media_analytics_detail_media_analytics" on
|
||||
"public"."media_analytics_detail" using btree ("media_analytics_id");
|
||||
@@ -1,4 +0,0 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."media_analytics" add column "unique_documents" numeric
|
||||
-- null;
|
||||
@@ -1,2 +0,0 @@
|
||||
alter table "public"."media_analytics" add column "unique_documents" numeric
|
||||
null;
|
||||
@@ -1,4 +0,0 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."media_analytics" add column "duplicate_documents" numeric
|
||||
-- null;
|
||||
@@ -1,2 +0,0 @@
|
||||
alter table "public"."media_analytics" add column "duplicate_documents" numeric
|
||||
null;
|
||||
@@ -1,4 +0,0 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."media_analytics_detail" add column "unique_documents" numeric
|
||||
-- null;
|
||||
@@ -1,2 +0,0 @@
|
||||
alter table "public"."media_analytics_detail" add column "unique_documents" numeric
|
||||
null;
|
||||
@@ -1,4 +0,0 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."media_analytics_detail" add column "duplicate_documents" numeric
|
||||
-- null;
|
||||
@@ -1,2 +0,0 @@
|
||||
alter table "public"."media_analytics_detail" add column "duplicate_documents" numeric
|
||||
null;
|
||||
@@ -1 +0,0 @@
|
||||
alter table "public"."media_analytics_detail" rename column "unique_document_count" to "unique_documents";
|
||||
@@ -1 +0,0 @@
|
||||
alter table "public"."media_analytics_detail" rename column "unique_documents" to "unique_document_count";
|
||||
@@ -1 +0,0 @@
|
||||
alter table "public"."media_analytics_detail" rename column "duplicate_count" to "duplicate_documents";
|
||||
@@ -1 +0,0 @@
|
||||
alter table "public"."media_analytics_detail" rename column "duplicate_documents" to "duplicate_count";
|
||||
7
package-lock.json
generated
7
package-lock.json
generated
@@ -23,6 +23,7 @@
|
||||
"archiver": "^7.0.1",
|
||||
"aws4": "^1.13.2",
|
||||
"axios": "^1.12.2",
|
||||
"axios-curlirize": "^2.0.0",
|
||||
"better-queue": "^3.8.12",
|
||||
"bullmq": "^5.61.0",
|
||||
"chart.js": "^4.5.0",
|
||||
@@ -3586,6 +3587,12 @@
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios-curlirize": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/axios-curlirize/-/axios-curlirize-2.0.0.tgz",
|
||||
"integrity": "sha512-TrQBa8MfIwaYsrCoYhfCr7NDRXLuGm+Rqh/PtAuO64b8PCCOJWn37BWQvpN4/mzzig3uHb4qXzvpxJmALHaiwA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios-ntlm": {
|
||||
"version": "1.4.6",
|
||||
"resolved": "https://registry.npmjs.org/axios-ntlm/-/axios-ntlm-1.4.6.tgz",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"archiver": "^7.0.1",
|
||||
"aws4": "^1.13.2",
|
||||
"axios": "^1.12.2",
|
||||
"axios-curlirize": "^2.0.0",
|
||||
"better-queue": "^3.8.12",
|
||||
"bullmq": "^5.61.0",
|
||||
"chart.js": "^4.5.0",
|
||||
|
||||
@@ -38,7 +38,7 @@ const { registerCleanupTask, initializeCleanupManager } = require("./server/util
|
||||
|
||||
const { loadEmailQueue } = require("./server/notifications/queues/emailQueue");
|
||||
const { loadAppQueue } = require("./server/notifications/queues/appQueue");
|
||||
|
||||
const { SetLegacyWebsocketHandlers } = require("./server/web-sockets/web-socket");
|
||||
const CLUSTER_RETRY_BASE_DELAY = 100;
|
||||
const CLUSTER_RETRY_MAX_DELAY = 5000;
|
||||
const CLUSTER_RETRY_JITTER = 100;
|
||||
@@ -324,6 +324,9 @@ const applySocketIO = async ({ server, app }) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Legacy Socket Events
|
||||
SetLegacyWebsocketHandlers(io)
|
||||
|
||||
const api = {
|
||||
pubClient,
|
||||
io,
|
||||
@@ -387,8 +390,6 @@ const main = async () => {
|
||||
const redisHelpers = applyRedisHelpers({ pubClient, app, logger });
|
||||
const ioHelpers = applyIOHelpers({ app, redisHelpers, ioRedis, logger });
|
||||
|
||||
// Legacy Socket Events
|
||||
require("./server/web-sockets/web-socket");
|
||||
|
||||
// Initialize Queues
|
||||
await loadQueues({ pubClient: pubClient, logger, redisHelpers, ioRedis });
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
||||
|
||||
const queries = require("../../graphql-client/queries");
|
||||
const CdkBase = require("../../web-sockets/web-socket");
|
||||
const WsLogger = require("../../web-sockets/createLogEvent");
|
||||
const moment = require("moment");
|
||||
const Dinero = require("dinero.js");
|
||||
const AxiosLib = require("axios").default;
|
||||
@@ -23,7 +23,7 @@ axios.interceptors.request.use((x) => {
|
||||
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
|
||||
//console.log(printable);
|
||||
|
||||
CdkBase.createJsonEvent(socket, "SILLY", `Raw Request: ${printable}`, x.data);
|
||||
WsLogger.createJsonEvent(socket, "SILLY", `Raw Request: ${printable}`, x.data);
|
||||
|
||||
return x;
|
||||
});
|
||||
@@ -33,14 +33,14 @@ axios.interceptors.response.use((x) => {
|
||||
|
||||
const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`;
|
||||
//console.log(printable);
|
||||
CdkBase.createJsonEvent(socket, "SILLY", `Raw Response: ${printable}`, x.data);
|
||||
WsLogger.createJsonEvent(socket, "SILLY", `Raw Response: ${printable}`, x.data);
|
||||
|
||||
return x;
|
||||
});
|
||||
|
||||
async function PbsCalculateAllocationsAp(socket, billids) {
|
||||
try {
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `Received request to calculate allocations for ${billids}`);
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `Received request to calculate allocations for ${billids}`);
|
||||
const { bills, bodyshops } = await QueryBillData(socket, billids);
|
||||
const bodyshop = bodyshops[0];
|
||||
socket.bodyshop = bodyshop;
|
||||
@@ -50,7 +50,7 @@ async function PbsCalculateAllocationsAp(socket, billids) {
|
||||
|
||||
const transactionlist = [];
|
||||
if (bills.length === 0) {
|
||||
CdkBase.createLogEvent(
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"ERROR",
|
||||
`No bills found for export. Ensure they have not already been exported and try again.`
|
||||
@@ -147,7 +147,9 @@ async function PbsCalculateAllocationsAp(socket, billids) {
|
||||
...billHash[key],
|
||||
Amount: billHash[key].Amount.toFormat("0.00")
|
||||
});
|
||||
APAmount = APAmount.add(billHash[key].Amount); //Calculate the total expense for the bill iteratively to create the corresponding credit to AP.
|
||||
//Calculate the total expense for the bill iteratively to
|
||||
// create the corresponding credit to AP.
|
||||
APAmount = APAmount.add(billHash[key].Amount);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -166,19 +168,21 @@ async function PbsCalculateAllocationsAp(socket, billids) {
|
||||
|
||||
return transactionlist;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsCalculateAllocationsAp. ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error encountered in PbsCalculateAllocationsAp. ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
exports.PbsCalculateAllocationsAp = PbsCalculateAllocationsAp;
|
||||
|
||||
async function QueryBillData(socket, billids) {
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `Querying bill data for id(s) ${billids}`);
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `Querying bill data for id(s) ${billids}`);
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
|
||||
|
||||
const result = await client
|
||||
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
|
||||
.request(queries.GET_PBS_AP_ALLOCATIONS, { billids: billids });
|
||||
CdkBase.createLogEvent(socket, "SILLY", `Bill data query result ${JSON.stringify(result, null, 2)}`);
|
||||
|
||||
WsLogger.createLogEvent(socket, "SILLY", `Bill data query result ${JSON.stringify(result, null, 2)}`);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -192,40 +196,10 @@ function getCostAccount(billline, respcenters) {
|
||||
return respcenters.costs.find((c) => c.name === acctName);
|
||||
}
|
||||
|
||||
exports.PbsExportAp = async function (socket, { billids, txEnvelope }) {
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `Exporting selected AP.`);
|
||||
|
||||
//apAllocations has the same shap as the lines key for the accounting posting to PBS.
|
||||
socket.apAllocations = await PbsCalculateAllocationsAp(socket, billids);
|
||||
socket.txEnvelope = txEnvelope;
|
||||
for (const allocation of socket.apAllocations) {
|
||||
const { billid, ...restAllocation } = allocation;
|
||||
const { data: AccountPostingChange } = await axios.post(PBS_ENDPOINTS.AccountingPostingChange, restAllocation, {
|
||||
auth: PBS_CREDENTIALS,
|
||||
socket
|
||||
});
|
||||
|
||||
CheckForErrors(socket, AccountPostingChange);
|
||||
|
||||
if (AccountPostingChange.WasSuccessful) {
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `Marking bill as exported.`);
|
||||
await MarkApExported(socket, [billid]);
|
||||
|
||||
socket.emit("ap-export-success", billid);
|
||||
} else {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Export was not successful.`);
|
||||
socket.emit("ap-export-failure", {
|
||||
billid,
|
||||
error: AccountPostingChange.Message
|
||||
});
|
||||
}
|
||||
}
|
||||
socket.emit("ap-export-complete");
|
||||
};
|
||||
|
||||
async function MarkApExported(socket, billids) {
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `Marking bills as exported for id ${billids}`);
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `Marking bills as exported for id ${billids}`);
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
|
||||
|
||||
const result = await client
|
||||
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
|
||||
.request(queries.MARK_BILLS_EXPORTED, {
|
||||
@@ -244,3 +218,36 @@ async function MarkApExported(socket, billids) {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const defaultHandler = async (socket, { billids, txEnvelope }) => {
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `Exporting selected AP.`);
|
||||
|
||||
//apAllocations has the same shap as the lines key for the accounting posting to PBS.
|
||||
socket.apAllocations = await PbsCalculateAllocationsAp(socket, billids);
|
||||
socket.txEnvelope = txEnvelope;
|
||||
for (const allocation of socket.apAllocations) {
|
||||
const { billid, ...restAllocation } = allocation;
|
||||
const { data: AccountPostingChange } = await axios.post(PBS_ENDPOINTS.AccountingPostingChange, restAllocation, {
|
||||
auth: PBS_CREDENTIALS,
|
||||
socket
|
||||
});
|
||||
|
||||
CheckForErrors(socket, AccountPostingChange);
|
||||
|
||||
if (AccountPostingChange.WasSuccessful) {
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `Marking bill as exported.`);
|
||||
await MarkApExported(socket, [billid]);
|
||||
|
||||
socket.emit("ap-export-success", billid);
|
||||
} else {
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Export was not successful.`);
|
||||
socket.emit("ap-export-failure", {
|
||||
billid,
|
||||
error: AccountPostingChange.Message
|
||||
});
|
||||
}
|
||||
}
|
||||
socket.emit("ap-export-complete");
|
||||
};
|
||||
|
||||
exports.PbsExportAp = defaultHandler;
|
||||
|
||||
@@ -2,10 +2,12 @@ const GraphQLClient = require("graphql-request").GraphQLClient;
|
||||
const AxiosLib = require("axios").default;
|
||||
const queries = require("../../graphql-client/queries");
|
||||
const { PBS_ENDPOINTS, PBS_CREDENTIALS } = require("./pbs-constants");
|
||||
const WsLogger = require("../../web-sockets/createLogEvent");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
//const { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl");
|
||||
const CalculateAllocations = require("../../cdk/cdk-calculate-allocations").default;
|
||||
const CdkBase = require("../../web-sockets/web-socket");
|
||||
const moment = require("moment-timezone");
|
||||
const Dinero = require("dinero.js");
|
||||
const InstanceManager = require("../../utils/instanceMgr").default;
|
||||
@@ -19,11 +21,12 @@ axios.interceptors.request.use((x) => {
|
||||
...x.headers[x.method],
|
||||
...x.headers
|
||||
};
|
||||
const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${x.url
|
||||
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
|
||||
const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${
|
||||
x.url
|
||||
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
|
||||
//logRequestToFile(printable);
|
||||
|
||||
CdkBase.createJsonEvent(socket, "DEBUG", `Raw Request: ${printable}`, x.data);
|
||||
WsLogger.createJsonEvent(socket, "DEBUG", `Raw Request: ${printable}`, x.data);
|
||||
|
||||
return x;
|
||||
});
|
||||
@@ -33,13 +36,11 @@ axios.interceptors.response.use((x) => {
|
||||
|
||||
const printable = `${new Date()} | Response: ${x.status} ${x.statusText} |${JSON.stringify(x.data)}`;
|
||||
//logRequestToFile(printable);
|
||||
CdkBase.createJsonEvent(socket, "DEBUG", `Raw Response: ${printable}`, x.data);
|
||||
WsLogger.createJsonEvent(socket, "DEBUG", `Raw Response: ${printable}`, x.data);
|
||||
|
||||
return x;
|
||||
});
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require("path");
|
||||
function logRequestToFile(printable) {
|
||||
try {
|
||||
const logDir = path.join(process.cwd(), "logs");
|
||||
@@ -53,21 +54,20 @@ function logRequestToFile(printable) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
exports.default = async function (socket, { txEnvelope, jobid }) {
|
||||
const defaultHandler = async (socket, { txEnvelope, jobid }) => {
|
||||
socket.logEvents = [];
|
||||
socket.recordid = jobid;
|
||||
socket.txEnvelope = txEnvelope;
|
||||
try {
|
||||
CdkBase.createLogEvent(socket, "INFO", `Received Job export request for id ${jobid}`);
|
||||
WsLogger.createLogEvent(socket, "INFO", `Received Job export request for id ${jobid}`);
|
||||
|
||||
const JobData = await QueryJobData(socket, jobid);
|
||||
socket.JobData = JobData;
|
||||
CdkBase.createLogEvent(socket, "INFO", `Querying the DMS for the Vehicle Record.`);
|
||||
WsLogger.createLogEvent(socket, "INFO", `Querying the DMS for the Vehicle Record.`);
|
||||
//Query for the Vehicle record to get the associated customer.
|
||||
socket.DmsVeh = await QueryVehicleFromDms(socket);
|
||||
//Todo: Need to validate the lines and methods below.
|
||||
if (socket.DmsVeh && socket.DmsVeh.CustomerRef) {
|
||||
if (socket.DmsVeh?.CustomerRef) {
|
||||
//Get the associated customer from the Vehicle Record.
|
||||
socket.DMSVehCustomer = await QueryCustomerBycodeFromDms(socket, socket.DmsVeh.CustomerRef);
|
||||
}
|
||||
@@ -78,35 +78,39 @@ exports.default = async function (socket, { txEnvelope, jobid }) {
|
||||
...socket.DMSCustList
|
||||
]);
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsJobExport. ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error encountered in PbsJobExport. ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
exports.default = defaultHandler;
|
||||
|
||||
exports.PbsSelectedCustomer = async function PbsSelectedCustomer(socket, selectedCustomerId) {
|
||||
try {
|
||||
socket.selectedCustomerId = selectedCustomerId;
|
||||
if (socket.JobData.bodyshop.pbs_configuration.disablecontactvehicle !== true) {
|
||||
CdkBase.createLogEvent(socket, "INFO", `User selected customer ${selectedCustomerId || "NEW"}`);
|
||||
WsLogger.createLogEvent(socket, "INFO", `User selected customer ${selectedCustomerId || "NEW"}`);
|
||||
|
||||
//Upsert the contact information as per Wafaa's Email.
|
||||
CdkBase.createLogEvent(
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"INFO",
|
||||
`Upserting contact information to DMS for ${socket.JobData.ownr_fn || ""
|
||||
`Upserting contact information to DMS for ${
|
||||
socket.JobData.ownr_fn || ""
|
||||
} ${socket.JobData.ownr_ln || ""} ${socket.JobData.ownr_co_nm || ""}`
|
||||
);
|
||||
const ownerRef = await UpsertContactData(socket, selectedCustomerId);
|
||||
socket.ownerRef = ownerRef;
|
||||
CdkBase.createLogEvent(socket, "INFO", `Upserting vehicle information to DMS for ${socket.JobData.v_vin}`);
|
||||
|
||||
WsLogger.createLogEvent(socket, "INFO", `Upserting vehicle information to DMS for ${socket.JobData.v_vin}`);
|
||||
const vehicleRef = await UpsertVehicleData(socket, ownerRef.ReferenceId);
|
||||
socket.vehicleRef = vehicleRef;
|
||||
} else {
|
||||
CdkBase.createLogEvent(
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"INFO",
|
||||
`Contact and Vehicle updates disabled. Querying data and skipping to accounting data insert.`
|
||||
);
|
||||
//Must query for records to insert $0 RO.
|
||||
//Must query for records to insert $0 RO.
|
||||
if (!socket.ownerRef) {
|
||||
const ownerRef = (await QueryCustomerBycodeFromDms(socket, selectedCustomerId))?.[0];
|
||||
socket.ownerRef = ownerRef;
|
||||
@@ -114,22 +118,21 @@ exports.PbsSelectedCustomer = async function PbsSelectedCustomer(socket, selecte
|
||||
const vehicleRef = await GetVehicleData(socket, socket.ownerRef?.ReferenceId || socket.selectedCustomerId);
|
||||
socket.vehicleRef = vehicleRef;
|
||||
}
|
||||
CdkBase.createLogEvent(socket, "INFO", `Inserting accounting posting data..`);
|
||||
WsLogger.createLogEvent(socket, "INFO", `Inserting account posting data...`);
|
||||
const insertResponse = await InsertAccountPostingData(socket);
|
||||
|
||||
if (insertResponse.WasSuccessful) {
|
||||
if (socket.JobData.bodyshop.pbs_configuration.ro_posting) {
|
||||
|
||||
await CreateRepairOrderInPBS(socket, socket.ownerRef, socket.vehicleRef)
|
||||
await CreateRepairOrderInPBS(socket, socket.ownerRef, socket.vehicleRef);
|
||||
}
|
||||
CdkBase.createLogEvent(socket, "INFO", `Marking job as exported.`);
|
||||
WsLogger.createLogEvent(socket, "INFO", `Marking job as exported.`);
|
||||
await MarkJobExported(socket, socket.JobData.id);
|
||||
socket.emit("export-success", socket.JobData.id);
|
||||
} else {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Export was not successful.`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Export was not successful.`);
|
||||
}
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsSelectedCustomer. ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error encountered in PbsSelectedCustomer. ${error}`);
|
||||
await InsertFailedExportLog(socket, error);
|
||||
}
|
||||
};
|
||||
@@ -137,22 +140,24 @@ exports.PbsSelectedCustomer = async function PbsSelectedCustomer(socket, selecte
|
||||
// Was Successful
|
||||
async function CheckForErrors(socket, response) {
|
||||
if (response.WasSuccessful === undefined || response.WasSuccessful === true) {
|
||||
CdkBase.createLogEvent(socket, "INFO", `Successful response from DMS. ${response.Message || ""}`);
|
||||
WsLogger.createLogEvent(socket, "INFO", `Successful response from DMS. ${response.Message || ""}`);
|
||||
} else {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error received from DMS: ${response.Message}`);
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `Error received from DMS: ${JSON.stringify(response)}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error received from DMS: ${response.Message}`);
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `Error received from DMS: ${JSON.stringify(response)}`);
|
||||
}
|
||||
}
|
||||
|
||||
exports.CheckForErrors = CheckForErrors;
|
||||
|
||||
async function QueryJobData(socket, jobid) {
|
||||
CdkBase.createLogEvent(socket, "INFO", `Querying job data for id ${jobid}`);
|
||||
WsLogger.createLogEvent(socket, "INFO", `Querying job data for id ${jobid}`);
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
|
||||
|
||||
const result = await client
|
||||
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
|
||||
.request(queries.QUERY_JOBS_FOR_PBS_EXPORT, { id: jobid });
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `Job data query result ${JSON.stringify(result, null, 2)}`);
|
||||
|
||||
//WsLogger.createLogEvent(socket, "DEBUG", `Job data query result ${JSON.stringify(result, null, 2)}`);
|
||||
return result.jobs_by_pk;
|
||||
}
|
||||
|
||||
@@ -191,7 +196,7 @@ async function QueryVehicleFromDms(socket) {
|
||||
CheckForErrors(socket, VehicleGetResponse);
|
||||
return VehicleGetResponse;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in QueryVehicleFromDms - ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in QueryVehicleFromDms - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -220,9 +225,9 @@ async function QueryCustomersFromDms(socket) {
|
||||
{ auth: PBS_CREDENTIALS, socket }
|
||||
);
|
||||
CheckForErrors(socket, CustomerGetResponse);
|
||||
return CustomerGetResponse && CustomerGetResponse.Contacts;
|
||||
return CustomerGetResponse?.Contacts;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in QueryCustomersFromDms - ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in QueryCustomersFromDms - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -253,9 +258,9 @@ async function QueryCustomerBycodeFromDms(socket, CustomerRef) {
|
||||
{ auth: PBS_CREDENTIALS, socket }
|
||||
);
|
||||
CheckForErrors(socket, CustomerGetResponse);
|
||||
return CustomerGetResponse && CustomerGetResponse.Contacts;
|
||||
return CustomerGetResponse?.Contacts;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in QueryCustomersFromDms - ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in QueryCustomersFromDms - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -272,15 +277,15 @@ async function UpsertContactData(socket, selectedCustomerId) {
|
||||
Code: socket.JobData.owner.accountingid,
|
||||
...(socket.JobData.ownr_co_nm
|
||||
? {
|
||||
//LastName: socket.JobData.ownr_ln,
|
||||
FirstName: socket.JobData.ownr_co_nm,
|
||||
IsBusiness: true
|
||||
}
|
||||
//LastName: socket.JobData.ownr_ln,
|
||||
FirstName: socket.JobData.ownr_co_nm,
|
||||
IsBusiness: true
|
||||
}
|
||||
: {
|
||||
LastName: socket.JobData.ownr_ln,
|
||||
FirstName: socket.JobData.ownr_fn,
|
||||
IsBusiness: false
|
||||
}),
|
||||
LastName: socket.JobData.ownr_ln,
|
||||
FirstName: socket.JobData.ownr_fn,
|
||||
IsBusiness: false
|
||||
}),
|
||||
|
||||
//Salutation: "String",
|
||||
//MiddleName: "String",
|
||||
@@ -337,7 +342,7 @@ async function UpsertContactData(socket, selectedCustomerId) {
|
||||
CheckForErrors(socket, ContactChangeResponse);
|
||||
return ContactChangeResponse;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in UpsertContactData - ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in UpsertContactData - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -357,10 +362,10 @@ async function UpsertVehicleData(socket, ownerRef) {
|
||||
//FleetNumber: "String",
|
||||
//Status: "String",
|
||||
OwnerRef: ownerRef, // "00000000000000000000000000000000",
|
||||
// ModelNumber: socket.JobData.vehicle && socket.JobData.vehicle.v_makecode,
|
||||
// ModelNumber: socket.JobData.vehicle?.v_makecode,
|
||||
Make: socket.JobData.v_make_desc,
|
||||
Model: socket.JobData.v_model_desc,
|
||||
Trim: socket.JobData.vehicle && socket.JobData.vehicle.v_trimcode,
|
||||
Trim: socket.JobData.vehicle?.v_trimcode,
|
||||
//VehicleType: "String",
|
||||
Year: socket.JobData.v_model_yr,
|
||||
Odometer: socket.JobData.kmout,
|
||||
@@ -490,14 +495,16 @@ async function UpsertVehicleData(socket, ownerRef) {
|
||||
CheckForErrors(socket, VehicleChangeResponse);
|
||||
return VehicleChangeResponse;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in UpsertVehicleData - ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in UpsertVehicleData - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function GetVehicleData(socket, ownerRef) {
|
||||
try {
|
||||
const { data: { Vehicles } } = await axios.post(
|
||||
const {
|
||||
data: { Vehicles }
|
||||
} = await axios.post(
|
||||
PBS_ENDPOINTS.VehicleGet,
|
||||
{
|
||||
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
|
||||
@@ -510,7 +517,7 @@ async function GetVehicleData(socket, ownerRef) {
|
||||
// "Trim": "String",
|
||||
// "ModelNumber": "String",
|
||||
// "StockNumber": "String",
|
||||
VIN: socket.JobData.v_vin,
|
||||
VIN: socket.JobData.v_vin
|
||||
// "LicenseNumber": "String",
|
||||
// "Lot": "String",
|
||||
// "Status": "String",
|
||||
@@ -528,24 +535,21 @@ async function GetVehicleData(socket, ownerRef) {
|
||||
// "LotAccessDivisions": [0],
|
||||
// "OdometerTo": 0,
|
||||
// "OdometerFrom": 0
|
||||
}
|
||||
,
|
||||
},
|
||||
{ auth: PBS_CREDENTIALS, socket }
|
||||
);
|
||||
CheckForErrors(socket, Vehicles);
|
||||
if (Vehicles.length === 1) {
|
||||
return Vehicles[0];
|
||||
|
||||
} else {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in Getting Vehicle Data - ${Vehicles.length} vehicle(s) found`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in Getting Vehicle Data - ${Vehicles.length} vehicle(s) found`);
|
||||
}
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in UpsertVehicleData - ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in UpsertVehicleData - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function InsertAccountPostingData(socket) {
|
||||
try {
|
||||
const allocations = await CalculateAllocations(socket, socket.JobData.id);
|
||||
@@ -629,7 +633,8 @@ async function InsertAccountPostingData(socket) {
|
||||
Posting: {
|
||||
Reference: socket.JobData.ro_number,
|
||||
JournalCode: socket.txEnvelope.journal,
|
||||
TransactionDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString(), //"0001-01-01T00:00:00.0000000Z",
|
||||
//Sample TransactionDate: "0001-01-01T00:00:00.0000000Z",
|
||||
TransactionDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString(),
|
||||
Description: socket.txEnvelope.story,
|
||||
//AdditionalInfo: "String",
|
||||
Source: InstanceManager({ imex: "ImEX Online", rome: "Rome Online" }),
|
||||
@@ -642,14 +647,15 @@ async function InsertAccountPostingData(socket) {
|
||||
CheckForErrors(socket, AccountPostingChange);
|
||||
return AccountPostingChange;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in InsertAccountPostingData - ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in InsertAccountPostingData - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function MarkJobExported(socket, jobid) {
|
||||
CdkBase.createLogEvent(socket, "INFO", `Marking job as exported for id ${jobid}`);
|
||||
WsLogger.createLogEvent(socket, "INFO", `Marking job as exported for id ${jobid}`);
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
|
||||
|
||||
const result = await client
|
||||
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
|
||||
.request(queries.MARK_JOB_EXPORTED, {
|
||||
@@ -677,47 +683,52 @@ async function MarkJobExported(socket, jobid) {
|
||||
async function InsertFailedExportLog(socket, error) {
|
||||
try {
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
|
||||
|
||||
const result = await client
|
||||
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
|
||||
.request(queries.INSERT_EXPORT_LOG, {
|
||||
log: {
|
||||
logs: [{
|
||||
bodyshopid: socket.JobData.bodyshop.id,
|
||||
jobid: socket.JobData.id,
|
||||
successful: false,
|
||||
message: JSON.stringify(error),
|
||||
useremail: socket.user.email
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (error2) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in InsertFailedExportLog - ${error} - ${JSON.stringify(error2)}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in InsertFailedExportLog - ${error} - ${JSON.stringify(error2)}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function CreateRepairOrderInPBS(socket) {
|
||||
try {
|
||||
const { RepairOrders } = await RepairOrderGet(socket);
|
||||
if (RepairOrders.length === 0) {
|
||||
const InsertedRepairOrder = await RepairOrderChange(socket)
|
||||
const InsertedRepairOrder = await RepairOrderChange(socket);
|
||||
socket.InsertedRepairOrder = InsertedRepairOrder;
|
||||
CdkBase.createLogEvent(socket, "INFO", `No repair orders found for vehicle. Inserting record.`);
|
||||
|
||||
WsLogger.createLogEvent(socket, "INFO", `No repair orders found for vehicle. Inserting record.`);
|
||||
} else if (RepairOrders.length > 0) {
|
||||
//Find out if it's a matching RO.
|
||||
//This logic is used because the integration will simply add another line to an open RO if it exists.
|
||||
const matchingRo = RepairOrders.find(ro => ro.Memo?.toLowerCase()?.includes(socket.JobData.ro_number.toLowerCase()))
|
||||
//Find out if it's a matching RO.
|
||||
//This logic is used because the integration will simply add another line to an open RO if it exists.
|
||||
const matchingRo = RepairOrders.find((ro) =>
|
||||
ro.Memo?.toLowerCase()?.includes(socket.JobData.ro_number.toLowerCase())
|
||||
);
|
||||
if (!matchingRo) {
|
||||
CdkBase.createLogEvent(socket, "INFO", `ROs found for vehicle, but none match. Inserting record.`);
|
||||
const InsertedRepairOrder = await RepairOrderChange(socket)
|
||||
WsLogger.createLogEvent(socket, "INFO", `ROs found for vehicle, but none match. Inserting record.`);
|
||||
const InsertedRepairOrder = await RepairOrderChange(socket);
|
||||
socket.InsertedRepairOrder = InsertedRepairOrder;
|
||||
} else {
|
||||
CdkBase.createLogEvent(socket, "WARN", `Repair order appears to already exist in PBS. ${matchingRo.RepairOrderNumber}`);
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"WARN",
|
||||
`Repair order appears to already exist in PBS. ${matchingRo.RepairOrderNumber}`
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in CreateRepairOrderInPBS - ${error} - ${JSON.stringify(error)}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in CreateRepairOrderInPBS - ${error} - ${JSON.stringify(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,7 +744,7 @@ async function RepairOrderGet(socket) {
|
||||
// "Tag": "String",
|
||||
//"ContactRef": socket.contactRef,
|
||||
// "ContactRefList": ["00000000000000000000000000000000"],
|
||||
"VehicleRef": socket.vehicleRef?.ReferenceId || socket.vehicleRef?.VehicleId,
|
||||
VehicleRef: socket.vehicleRef?.ReferenceId || socket.vehicleRef?.VehicleId
|
||||
// "VehicleRefList": ["00000000000000000000000000000000"],
|
||||
// "Status": "String",
|
||||
// "CashieredSince": "0001-01-01T00:00:00.0000000Z",
|
||||
@@ -749,7 +760,7 @@ async function RepairOrderGet(socket) {
|
||||
CheckForErrors(socket, RepairOrderGet);
|
||||
return RepairOrderGet;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in RepairOrderChange - ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in RepairOrderChange - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -758,31 +769,32 @@ async function RepairOrderChange(socket) {
|
||||
try {
|
||||
const { data: RepairOrderChangeResponse } = await axios.post(
|
||||
PBS_ENDPOINTS.RepairOrderChange,
|
||||
{ //Additional details at https://partnerhub.pbsdealers.com/json/metadata?op=RepairOrderChange
|
||||
"RepairOrderInfo": {
|
||||
{
|
||||
//Additional details at https://partnerhub.pbsdealers.com/json/metadata?op=RepairOrderChange
|
||||
RepairOrderInfo: {
|
||||
//"Id": "string/00000000-0000-0000-0000-000000000000",
|
||||
//"RepairOrderId": "00000000000000000000000000000000",
|
||||
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
|
||||
"RepairOrderNumber": "00000000000000000000000000000000", //This helps force a new RO.
|
||||
"RawRepairOrderNumber": "00000000000000000000000000000000",
|
||||
RepairOrderNumber: "00000000000000000000000000000000", //This helps force a new RO.
|
||||
RawRepairOrderNumber: "00000000000000000000000000000000",
|
||||
// "RepairOrderNumber": socket.JobData.ro_number, //These 2 values are ignored as confirmed by PBS.
|
||||
// "RawRepairOrderNumber": socket.JobData.ro_number,
|
||||
"DateOpened": moment(),
|
||||
DateOpened: moment(),
|
||||
// "DateOpenedUTC": "0001-01-01T00:00:00.0000000Z",
|
||||
// "DateCashiered": "0001-01-01T00:00:00.0000000Z",
|
||||
// "DateCashieredUTC": "0001-01-01T00:00:00.0000000Z",
|
||||
"DatePromised": socket.JobData.scheduled_completion,
|
||||
DatePromised: socket.JobData.scheduled_completion,
|
||||
// "DatePromisedUTC": "0001-01-01T00:00:00.0000000Z",
|
||||
"DateVehicleCompleted": socket.JobData.actual_completion,
|
||||
DateVehicleCompleted: socket.JobData.actual_completion,
|
||||
// "DateCustomerNotified": "0001-01-01T00:00:00.0000000Z",
|
||||
// "CSR": "String",
|
||||
// "CSRRef": "00000000000000000000000000000000",
|
||||
// "BookingUser": "String",
|
||||
// "BookingUserRef": "00000000000000000000000000000000",
|
||||
"ContactRef": socket.ownerRef?.ReferenceId || socket.ownerRef?.ContactId,
|
||||
"VehicleRef": socket.vehicleRef?.ReferenceId || socket.vehicleRef?.VehicleId,
|
||||
"MileageIn": socket.JobData.km_in,
|
||||
"Tag": "BODYSHOP",
|
||||
ContactRef: socket.ownerRef?.ReferenceId || socket.ownerRef?.ContactId,
|
||||
VehicleRef: socket.vehicleRef?.ReferenceId || socket.vehicleRef?.VehicleId,
|
||||
MileageIn: socket.JobData.km_in,
|
||||
Tag: "BODYSHOP",
|
||||
//"Status": "CLOSED", //Values here do not impact the status. Confirmed by PBS support.
|
||||
Requests: [
|
||||
{
|
||||
@@ -791,61 +803,59 @@ async function RepairOrderChange(socket) {
|
||||
// "CSR": "PBS",
|
||||
// "CSRRef": "1ce12ac692564e94bda955d529ee911a",
|
||||
// "Skill": "GEN",
|
||||
"RequestCode": "MISC",
|
||||
"RequestDescription": `VEHICLE REPAIRED AT BODYSHOP. PLEASE REFERENCE IMEX SHOP MANAGEMENT SYSTEM. ${socket.txEnvelope.story}`,
|
||||
"Status": "Completed",
|
||||
RequestCode: "MISC",
|
||||
RequestDescription: `VEHICLE REPAIRED AT BODYSHOP. PLEASE REFERENCE IMEX SHOP MANAGEMENT SYSTEM. ${socket.txEnvelope.story}`,
|
||||
Status: "Completed",
|
||||
// "TechRef": "00000000000000000000000000000000",
|
||||
"AllowedHours": 0,
|
||||
"EstimateLabour": 0,
|
||||
"EstimateParts": 0,
|
||||
"ComeBack": false,
|
||||
"AddedOperation": true,
|
||||
"PartLines": [],
|
||||
"PartRequestLines": [],
|
||||
"LabourLines": [],
|
||||
"SubletLines": [],
|
||||
"TimePunches": [],
|
||||
"Summary": {
|
||||
"Labour": 0,
|
||||
"Parts": 0,
|
||||
"OilGas": 0,
|
||||
"SubletTow": 0,
|
||||
"Misc": 0,
|
||||
"Environment": 0,
|
||||
"ShopSupplies": 0,
|
||||
"Freight": 0,
|
||||
"WarrantyDeductible": 0,
|
||||
"Discount": 0,
|
||||
"SubTotal": 0,
|
||||
"Tax1": 0,
|
||||
"Tax2": 0,
|
||||
"InvoiceTotal": 0,
|
||||
"CustomerDeductible": 0,
|
||||
"GrandTotal": 0,
|
||||
"LabourDiscount": 0,
|
||||
"PartDiscount": 0,
|
||||
"ServiceFeeTotal": 0,
|
||||
"OEMDiscount": 0
|
||||
AllowedHours: 0,
|
||||
EstimateLabour: 0,
|
||||
EstimateParts: 0,
|
||||
ComeBack: false,
|
||||
AddedOperation: true,
|
||||
PartLines: [],
|
||||
PartRequestLines: [],
|
||||
LabourLines: [],
|
||||
SubletLines: [],
|
||||
TimePunches: [],
|
||||
Summary: {
|
||||
Labour: 0,
|
||||
Parts: 0,
|
||||
OilGas: 0,
|
||||
SubletTow: 0,
|
||||
Misc: 0,
|
||||
Environment: 0,
|
||||
ShopSupplies: 0,
|
||||
Freight: 0,
|
||||
WarrantyDeductible: 0,
|
||||
Discount: 0,
|
||||
SubTotal: 0,
|
||||
Tax1: 0,
|
||||
Tax2: 0,
|
||||
InvoiceTotal: 0,
|
||||
CustomerDeductible: 0,
|
||||
GrandTotal: 0,
|
||||
LabourDiscount: 0,
|
||||
PartDiscount: 0,
|
||||
ServiceFeeTotal: 0,
|
||||
OEMDiscount: 0
|
||||
},
|
||||
"LineType": "RequestLine",
|
||||
},
|
||||
LineType: "RequestLine"
|
||||
}
|
||||
],
|
||||
|
||||
"Memo": socket.txEnvelope.story,
|
||||
|
||||
Memo: socket.txEnvelope.story
|
||||
},
|
||||
"IsAsynchronous": false,
|
||||
IsAsynchronous: false
|
||||
// "UserRequest": "String",
|
||||
// "UserRef": "00000000000000000000000000000000"
|
||||
}
|
||||
},
|
||||
|
||||
,
|
||||
{ auth: PBS_CREDENTIALS, socket }
|
||||
);
|
||||
CheckForErrors(socket, RepairOrderChangeResponse);
|
||||
return RepairOrderChangeResponse;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in RepairOrderChange - ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in RepairOrderChange - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
||||
|
||||
const queries = require("../graphql-client/queries");
|
||||
const CdkBase = require("../web-sockets/web-socket");
|
||||
|
||||
const CreateFortellisLogEvent = require("../fortellis/fortellis-logger");
|
||||
const Dinero = require("dinero.js");
|
||||
const _ = require("lodash");
|
||||
const WsLogger = require("../web-sockets/createLogEvent")
|
||||
|
||||
const InstanceManager = require("../utils/instanceMgr").default;
|
||||
const { DiscountNotAlreadyCounted } = InstanceManager({
|
||||
imex: require("../job/job-totals"),
|
||||
@@ -13,37 +14,40 @@ const { DiscountNotAlreadyCounted } = InstanceManager({
|
||||
|
||||
exports.defaultRoute = async function (req, res) {
|
||||
try {
|
||||
CdkBase.createLogEvent(req, "DEBUG", `Received request to calculate allocations for ${req.body.jobid}`);
|
||||
WsLogger.createLogEvent(req, "DEBUG", `Received request to calculate allocations for ${req.body.jobid}`);
|
||||
const jobData = await QueryJobData(req, req.BearerToken, req.body.jobid);
|
||||
return res.status(200).json({ data: calculateAllocations(req, jobData) });
|
||||
} catch (error) {
|
||||
////console.log(error);
|
||||
CdkBase.createLogEvent(req, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`);
|
||||
WsLogger.createLogEvent(req, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`);
|
||||
WsLogger.createLogEvent(req, "ERROR", `Error encountered in CdkCalculateAllocations. ${error.stack}`);
|
||||
res.status(500).json({ error: `Error encountered in CdkCalculateAllocations. ${error}` });
|
||||
}
|
||||
};
|
||||
|
||||
exports.default = async function (socket, jobid) {
|
||||
exports.default = async function (socket, jobid, isFortellis = false) {
|
||||
try {
|
||||
const jobData = await QueryJobData(socket, "Bearer " + socket.handshake.auth.token, jobid);
|
||||
return calculateAllocations(socket, jobData);
|
||||
const jobData = await QueryJobData(socket, "Bearer " + socket.handshake.auth.token, jobid, isFortellis);
|
||||
return calculateAllocations(socket, jobData, isFortellis);
|
||||
} catch (error) {
|
||||
////console.log(error);
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`);
|
||||
const loggingFunction = isFortellis ? CreateFortellisLogEvent : WsLogger.createLogEvent;
|
||||
loggingFunction(socket, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`);
|
||||
loggingFunction(socket, "ERROR", `Error encountered in CdkCalculateAllocations. ${error.stack}`);
|
||||
}
|
||||
};
|
||||
|
||||
async function QueryJobData(connectionData, token, jobid) {
|
||||
CdkBase.createLogEvent(connectionData, "DEBUG", `Querying job data for id ${jobid}`);
|
||||
async function QueryJobData(connectionData, token, jobid, isFortellis) {
|
||||
const loggingFunction = isFortellis ? CreateFortellisLogEvent : WsLogger.createLogEvent;
|
||||
|
||||
loggingFunction(connectionData, "DEBUG", `Querying job data for id ${jobid}`);
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
|
||||
const result = await client.setHeaders({ Authorization: token }).request(queries.GET_CDK_ALLOCATIONS, { id: jobid });
|
||||
CdkBase.createLogEvent(connectionData, "SILLY", `Job data query result ${JSON.stringify(result, null, 2)}`);
|
||||
//loggingFunction(connectionData, "DEBUG", `Job data query result ${JSON.stringify(result, null, 2)}`);
|
||||
return result.jobs_by_pk;
|
||||
}
|
||||
|
||||
function calculateAllocations(connectionData, job) {
|
||||
function calculateAllocations(connectionData, job, isFortellis) {
|
||||
const { bodyshop } = job;
|
||||
|
||||
const loggingFunction = isFortellis ? CreateFortellisLogEvent : WsLogger.createLogEvent;
|
||||
const taxAllocations = InstanceManager({
|
||||
executeFunction: true,
|
||||
deubg: true,
|
||||
@@ -132,11 +136,11 @@ function calculateAllocations(connectionData, job) {
|
||||
? val.prt_dsmk_m
|
||||
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
|
||||
: Dinero({
|
||||
amount: Math.round(val.act_price * 100)
|
||||
})
|
||||
.multiply(val.part_qty || 0)
|
||||
.percentage(Math.abs(val.prt_dsmk_p || 0))
|
||||
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
|
||||
amount: Math.round(val.act_price * 100)
|
||||
})
|
||||
.multiply(val.part_qty || 0)
|
||||
.percentage(Math.abs(val.prt_dsmk_p || 0))
|
||||
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
|
||||
: Dinero()
|
||||
);
|
||||
|
||||
@@ -159,7 +163,7 @@ function calculateAllocations(connectionData, job) {
|
||||
const selectedDmsAllocationConfig = bodyshop.md_responsibility_centers.dms_defaults.find(
|
||||
(d) => d.name === job.dms_allocation
|
||||
);
|
||||
CdkBase.createLogEvent(
|
||||
loggingFunction(
|
||||
connectionData,
|
||||
"DEBUG",
|
||||
`Using DMS Allocation ${selectedDmsAllocationConfig && selectedDmsAllocationConfig.name} for cost export.`
|
||||
@@ -194,8 +198,8 @@ function calculateAllocations(connectionData, job) {
|
||||
let TicketTotal = Dinero({
|
||||
amount: Math.round(
|
||||
ticket.rate *
|
||||
(ticket.employee && ticket.employee.flat_rate ? ticket.productivehrs || 0 : ticket.actualhrs || 0) *
|
||||
100
|
||||
(ticket.employee && ticket.employee.flat_rate ? ticket.productivehrs || 0 : ticket.actualhrs || 0) *
|
||||
100
|
||||
)
|
||||
});
|
||||
//Add it to the right cost center.
|
||||
@@ -360,10 +364,10 @@ function calculateAllocations(connectionData, job) {
|
||||
Dinero(job.job_totals.parts.adjustments[key])
|
||||
);
|
||||
} else {
|
||||
CdkBase.createLogEvent(
|
||||
loggingFunction(
|
||||
connectionData,
|
||||
"ERROR",
|
||||
`Error encountered in CdkCalculateAllocations. Unable to find adjustment account. ${error}`
|
||||
`Error encountered in CdkCalculateAllocations. Unable to find adjustment account: ${accountName}`
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -383,10 +387,10 @@ function calculateAllocations(connectionData, job) {
|
||||
Dinero(job.job_totals.rates[key].adjustments)
|
||||
);
|
||||
} else {
|
||||
CdkBase.createLogEvent(
|
||||
loggingFunction(
|
||||
connectionData,
|
||||
"ERROR",
|
||||
`Error encountered in CdkCalculateAllocations. Unable to find adjustment account. ${error}`
|
||||
`Error encountered in CdkCalculateAllocations. Unable to find adjustment account: ${accountName}`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -427,37 +431,37 @@ function calculateAllocations(connectionData, job) {
|
||||
|
||||
...(job.job_totals.totals.ttl_adjustment
|
||||
? [
|
||||
{
|
||||
center: "SUB ADJ",
|
||||
sale: Dinero(job.job_totals.totals.ttl_adjustment),
|
||||
cost: Dinero(),
|
||||
profitCenter: {
|
||||
name: "SUB ADJ",
|
||||
accountdesc: "SUB ADJ",
|
||||
accountitem: "SUB ADJ",
|
||||
accountname: "SUB ADJ",
|
||||
dms_acctnumber: bodyshop.md_responsibility_centers.ttl_adjustment.dms_acctnumber
|
||||
},
|
||||
costCenter: {}
|
||||
}
|
||||
]
|
||||
{
|
||||
center: "SUB ADJ",
|
||||
sale: Dinero(job.job_totals.totals.ttl_adjustment),
|
||||
cost: Dinero(),
|
||||
profitCenter: {
|
||||
name: "SUB ADJ",
|
||||
accountdesc: "SUB ADJ",
|
||||
accountitem: "SUB ADJ",
|
||||
accountname: "SUB ADJ",
|
||||
dms_acctnumber: bodyshop.md_responsibility_centers.ttl_adjustment.dms_acctnumber
|
||||
},
|
||||
costCenter: {}
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(job.job_totals.totals.ttl_tax_adjustment
|
||||
? [
|
||||
{
|
||||
center: "TAX ADJ",
|
||||
sale: Dinero(job.job_totals.totals.ttl_tax_adjustment),
|
||||
cost: Dinero(),
|
||||
profitCenter: {
|
||||
name: "TAX ADJ",
|
||||
accountdesc: "TAX ADJ",
|
||||
accountitem: "TAX ADJ",
|
||||
accountname: "TAX ADJ",
|
||||
dms_acctnumber: bodyshop.md_responsibility_centers.ttl_tax_adjustment.dms_acctnumber
|
||||
},
|
||||
costCenter: {}
|
||||
}
|
||||
]
|
||||
{
|
||||
center: "TAX ADJ",
|
||||
sale: Dinero(job.job_totals.totals.ttl_tax_adjustment),
|
||||
cost: Dinero(),
|
||||
profitCenter: {
|
||||
name: "TAX ADJ",
|
||||
accountdesc: "TAX ADJ",
|
||||
accountitem: "TAX ADJ",
|
||||
accountname: "TAX ADJ",
|
||||
dms_acctnumber: bodyshop.md_responsibility_centers.ttl_tax_adjustment.dms_acctnumber
|
||||
},
|
||||
costCenter: {}
|
||||
}
|
||||
]
|
||||
: [])
|
||||
];
|
||||
}
|
||||
|
||||
@@ -5,6 +5,12 @@ const CdkWsdl = require("./cdk-wsdl").default;
|
||||
const logger = require("../utils/logger");
|
||||
|
||||
const { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl");
|
||||
const {
|
||||
MakeFortellisCall,
|
||||
FortellisActions,
|
||||
GetAuthToken,
|
||||
GetDepartmentId
|
||||
} = require("../fortellis/fortellis-helpers");
|
||||
|
||||
// exports.default = async function (socket, cdk_dealerid) {
|
||||
// try {
|
||||
@@ -105,3 +111,85 @@ async function GetCdkMakes(req, cdk_dealerid) {
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function GetFortellisMakes(req, cdk_dealerid) {
|
||||
logger.log("fortellis-replace-makes-models", "DEBUG", req.user.email, null, {
|
||||
cdk_dealerid
|
||||
});
|
||||
try {
|
||||
const result = await MakeFortellisCall({
|
||||
...FortellisActions.GetMakeModel,
|
||||
headers: {},
|
||||
redisHelpers: {
|
||||
setSessionTransactionData: () => {
|
||||
return null;
|
||||
},
|
||||
getSessionTransactionData: () => {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
socket: { emit: () => null },
|
||||
jobid: null,
|
||||
body: {},
|
||||
SubscriptionObject: {
|
||||
SubscriptionID: cdk_dealerid
|
||||
}
|
||||
});
|
||||
|
||||
logger.log("fortellis-replace-makes-models-response", "ERROR", req.user.email, null, {
|
||||
cdk_dealerid,
|
||||
xml: result
|
||||
});
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
logger.log("fortellis-replace-makes-models-error", "ERROR", req.user.email, null, {
|
||||
cdk_dealerid,
|
||||
error
|
||||
});
|
||||
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
exports.fortellis = async function ReloadFortellisMakes(req, res) {
|
||||
const { bodyshopid, cdk_dealerid } = req.body;
|
||||
try {
|
||||
//Query all CDK Models
|
||||
const newList = await GetFortellisMakes(req, cdk_dealerid);
|
||||
|
||||
const BearerToken = req.BearerToken;
|
||||
const client = req.userGraphQLClient;
|
||||
|
||||
const deleteResult = await client
|
||||
.setHeaders({ Authorization: BearerToken })
|
||||
.request(queries.DELETE_ALL_DMS_VEHICLES, {});
|
||||
|
||||
//Insert the new ones.
|
||||
|
||||
const insertResult = await client.setHeaders({ Authorization: BearerToken }).request(queries.INSERT_DMS_VEHICLES, {
|
||||
vehicles: newList.map((i) => {
|
||||
return {
|
||||
bodyshopid,
|
||||
makecode: i.makeCode,
|
||||
modelcode: i.modelCode,
|
||||
make: i.makeFullName,
|
||||
model: i.modelFullName
|
||||
};
|
||||
})
|
||||
});
|
||||
|
||||
logger.log("fortellis-replace-makes-models-success", "DEBUG", req.user.email, null, {
|
||||
cdk_dealerid,
|
||||
count: newList.length
|
||||
});
|
||||
|
||||
res.sendStatus(200);
|
||||
} catch (error) {
|
||||
logger.log("fortellis-replace-makes-models-error", "ERROR", req.user.email, null, {
|
||||
cdk_dealerid,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
res.status(500).json(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,33 +1,36 @@
|
||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
||||
const soap = require("soap");
|
||||
const queries = require("../graphql-client/queries");
|
||||
const CdkBase = require("../web-sockets/web-socket");
|
||||
const CdkWsdl = require("./cdk-wsdl").default;
|
||||
const { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl");
|
||||
const CalcualteAllocations = require("./cdk-calculate-allocations").default;
|
||||
const InstanceMgr = require("../utils/instanceMgr").default;
|
||||
const WsLogger = require("../web-sockets/createLogEvent");
|
||||
|
||||
const moment = require("moment-timezone");
|
||||
|
||||
const replaceSpecialRegex = /[^a-zA-Z0-9 .,\n #]+/g;
|
||||
|
||||
exports.default = async function (socket, { txEnvelope, jobid }) {
|
||||
const defaultHandler = async (socket, { txEnvelope, jobid }) => {
|
||||
////Store the following information into the redis store for this transaction.
|
||||
socket.logEvents = [];
|
||||
socket.recordid = jobid;
|
||||
socket.txEnvelope = txEnvelope;
|
||||
////
|
||||
|
||||
try {
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `Received Job export request for id ${jobid}`);
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `Received Job export request for id ${jobid}`);
|
||||
|
||||
const JobData = await QueryJobData(socket, jobid);
|
||||
socket.JobData = JobData;
|
||||
const DealerId = JobData.bodyshop.cdk_dealerid;
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `Dealer ID detected: ${JSON.stringify(DealerId)}`);
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `Dealer ID detected: ${JSON.stringify(DealerId)}`);
|
||||
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `{1} Begin Calculate DMS Vehicle ID using VIN: ${JobData.v_vin}`);
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `{1} Begin Calculate DMS Vehicle ID using VIN: ${JobData.v_vin}`);
|
||||
socket.DMSVid = await CalculateDmsVid(socket, JobData);
|
||||
|
||||
if (socket.DMSVid.newId === "N") {
|
||||
CdkBase.createLogEvent(
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`{2.1} Querying the Vehicle using the DMSVid: ${socket.DMSVid.vehiclesVehId}`
|
||||
@@ -35,10 +38,10 @@ exports.default = async function (socket, { txEnvelope, jobid }) {
|
||||
socket.DMSVeh = await QueryDmsVehicleById(socket, JobData, socket.DMSVid);
|
||||
|
||||
const DMSVehCustomer =
|
||||
socket.DMSVeh && socket.DMSVeh.owners && socket.DMSVeh.owners.find((o) => o.id.assigningPartyId === "CURRENT");
|
||||
socket.DMSVeh?.owners && socket.DMSVeh.owners.find((o) => o.id.assigningPartyId === "CURRENT");
|
||||
|
||||
if (DMSVehCustomer && DMSVehCustomer.id && DMSVehCustomer.id.value) {
|
||||
CdkBase.createLogEvent(
|
||||
if (DMSVehCustomer?.id && DMSVehCustomer.id.value) {
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`{2.2} Querying the Customer using the ID from DMSVeh: ${DMSVehCustomer.id.value}`
|
||||
@@ -47,7 +50,7 @@ exports.default = async function (socket, { txEnvelope, jobid }) {
|
||||
}
|
||||
}
|
||||
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `{2.3} Querying the Customer using the name.`);
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `{2.3} Querying the Customer using the name.`);
|
||||
|
||||
socket.DMSCustList = await QueryDmsCustomerByName(socket, JobData);
|
||||
|
||||
@@ -56,43 +59,48 @@ exports.default = async function (socket, { txEnvelope, jobid }) {
|
||||
...socket.DMSCustList
|
||||
]);
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error encountered in CdkJobExport. ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error encountered in CdkJobExport. ${error}`);
|
||||
}
|
||||
};
|
||||
exports.default = defaultHandler;
|
||||
|
||||
async function CdkSelectedCustomer(socket, selectedCustomerId) {
|
||||
try {
|
||||
socket.selectedCustomerId = selectedCustomerId;
|
||||
if (selectedCustomerId) {
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `{3.1} Querying the Customer using Customer ID: ${selectedCustomerId}`);
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `{3.1} Querying the Customer using Customer ID: ${selectedCustomerId}`);
|
||||
socket.DMSCust = await QueryDmsCustomerById(socket, socket.JobData, selectedCustomerId);
|
||||
} else {
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `{3.2} Generating a new customer ID.`);
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `{3.2} Generating a new customer ID.`);
|
||||
const newCustomerId = await GenerateDmsCustomerNumber(socket);
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `{3.3} Inserting new customer with ID: ${newCustomerId}`);
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `{3.3} Inserting new customer with ID: ${newCustomerId}`);
|
||||
socket.DMSCust = await InsertDmsCustomer(socket, newCustomerId);
|
||||
}
|
||||
|
||||
if (socket.DMSVid.newId === "Y") {
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `{4.1} Inserting new vehicle with ID: ID ${socket.DMSVid.vehiclesVehId}`);
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`{4.1} Inserting new vehicle with ID: ID ${socket.DMSVid.vehiclesVehId}`
|
||||
);
|
||||
socket.DMSVeh = await InsertDmsVehicle(socket);
|
||||
} else {
|
||||
CdkBase.createLogEvent(
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`{4.2} Querying Existing Vehicle using ID ${socket.DMSVid.vehiclesVehId}`
|
||||
);
|
||||
socket.DMSVeh = await QueryDmsVehicleById(socket, socket.JobData, socket.DMSVid);
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `{4.3} Updating Existing Vehicle to associate to owner.`);
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `{4.3} Updating Existing Vehicle to associate to owner.`);
|
||||
socket.DMSVeh = await UpdateDmsVehicle(socket);
|
||||
}
|
||||
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `{5} Creating Transaction header with Dms Start WIP`);
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `{5} Creating Transaction header with Dms Start WIP`);
|
||||
socket.DMSTransHeader = await InsertDmsStartWip(socket);
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `{5.1} Creating Transaction with ID ${socket.DMSTransHeader.transID}`);
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `{5.1} Creating Transaction with ID ${socket.DMSTransHeader.transID}`);
|
||||
|
||||
socket.DMSBatchTxn = await InsertDmsBatchWip(socket);
|
||||
CdkBase.createLogEvent(
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`{6} Attempting to post Transaction with ID ${socket.DMSTransHeader.transID}`
|
||||
@@ -100,23 +108,23 @@ async function CdkSelectedCustomer(socket, selectedCustomerId) {
|
||||
socket.DmsBatchTxnPost = await PostDmsBatchWip(socket);
|
||||
if (socket.DmsBatchTxnPost.code === "success") {
|
||||
//something
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `{6} Successfully posted sransaction to DMS.`);
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `{6} Successfully posted sransaction to DMS.`);
|
||||
|
||||
await MarkJobExported(socket, socket.JobData.id);
|
||||
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `{5} Updating Service Vehicle History.`);
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `{5} Updating Service Vehicle History.`);
|
||||
socket.DMSVehHistory = await InsertServiceVehicleHistory(socket);
|
||||
socket.emit("export-success", socket.JobData.id);
|
||||
} else {
|
||||
//Get the error code
|
||||
CdkBase.createLogEvent(
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`{6.1} Getting errors for Transaction ID ${socket.DMSTransHeader.transID}`
|
||||
);
|
||||
socket.DmsError = await QueryDmsErrWip(socket);
|
||||
//Delete the transaction
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `{6.2} Deleting Transaction ID ${socket.DMSTransHeader.transID}`);
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `{6.2} Deleting Transaction ID ${socket.DMSTransHeader.transID}`);
|
||||
socket.DmsBatchTxnPost = await DeleteDmsWip(socket);
|
||||
|
||||
socket.DmsError.errMsg
|
||||
@@ -125,29 +133,33 @@ async function CdkSelectedCustomer(socket, selectedCustomerId) {
|
||||
(e) =>
|
||||
e !== null &&
|
||||
e !== "" &&
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error(s) encountered in posting transaction. ${e}`)
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error(s) encountered in posting transaction. ${e}`)
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error encountered in CdkSelectedCustomer. ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error encountered in CdkSelectedCustomer. ${error}`);
|
||||
await InsertFailedExportLog(socket, error);
|
||||
} finally {
|
||||
//Ensure we always insert logEvents
|
||||
//GQL to insert logevents.
|
||||
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `Capturing log events to database.`);
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `Capturing log events to database.`);
|
||||
}
|
||||
}
|
||||
|
||||
exports.CdkSelectedCustomer = CdkSelectedCustomer;
|
||||
|
||||
async function QueryJobData(socket, jobid) {
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `Querying job data for id ${jobid}`);
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `Querying job data for id ${jobid}`);
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
|
||||
const currentToken =
|
||||
(socket?.data && socket.data.authToken) || (socket?.handshake?.auth && socket.handshake.auth.token);
|
||||
|
||||
const result = await client
|
||||
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
|
||||
.setHeaders({ Authorization: `Bearer ${currentToken}` })
|
||||
.request(queries.QUERY_JOBS_FOR_CDK_EXPORT, { id: jobid });
|
||||
CdkBase.createLogEvent(socket, "SILLY", `Job data query result ${JSON.stringify(result, null, 2)}`);
|
||||
|
||||
//WsLogger.createLogEvent(socket, "SILLY", `Job data query result ${JSON.stringify(result, null, 2)}`);
|
||||
return result.jobs_by_pk;
|
||||
}
|
||||
|
||||
@@ -161,11 +173,11 @@ async function CalculateDmsVid(socket, JobData) {
|
||||
});
|
||||
|
||||
const [result, rawResponse, , rawRequest] = soapResponseVehicleInsertUpdate;
|
||||
CdkBase.createXmlEvent(socket, rawRequest, `soapClientVehicleInsertUpdate.getVehIdsAsync request.`);
|
||||
WsLogger.createXmlEvent(socket, rawRequest, `soapClientVehicleInsertUpdate.getVehIdsAsync request.`);
|
||||
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientVehicleInsertUpdate.getVehIdsAsync response.`);
|
||||
WsLogger.createXmlEvent(socket, rawResponse, `soapClientVehicleInsertUpdate.getVehIdsAsync response.`);
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"SILLY",
|
||||
`soapClientVehicleInsertUpdate.getVehIdsAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
@@ -178,15 +190,15 @@ async function CalculateDmsVid(socket, JobData) {
|
||||
|
||||
//return result && result.return && result.return[0];
|
||||
} catch (error) {
|
||||
CdkBase.createXmlEvent(socket, error.request, `soapClientVehicleInsertUpdate.getVehIdsAsync request.`, true);
|
||||
WsLogger.createXmlEvent(socket, error.request, `soapClientVehicleInsertUpdate.getVehIdsAsync request.`, true);
|
||||
|
||||
CdkBase.createXmlEvent(
|
||||
WsLogger.createXmlEvent(
|
||||
socket,
|
||||
error.response && error.response.data,
|
||||
error.response?.data,
|
||||
`soapClientVehicleInsertUpdate.getVehIdsAsync response.`,
|
||||
true
|
||||
);
|
||||
CdkBase.createLogEvent(socket, "ERROR", `{1} Error in CalculateDmsVid - ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `{1} Error in CalculateDmsVid - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -206,19 +218,19 @@ async function QueryDmsVehicleById(socket, JobData, DMSVid) {
|
||||
|
||||
const [result, rawResponse, , rawRequest] = soapResponseVehicleInsertUpdate;
|
||||
|
||||
CdkBase.createXmlEvent(socket, rawRequest, `soapClientVehicleInsertUpdate.readAsync request.`);
|
||||
WsLogger.createXmlEvent(socket, rawRequest, `soapClientVehicleInsertUpdate.readAsync request.`);
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"SILLY",
|
||||
`soapClientVehicleInsertUpdate.readAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientVehicleInsertUpdate.readAsync response.`);
|
||||
WsLogger.createXmlEvent(socket, rawResponse, `soapClientVehicleInsertUpdate.readAsync response.`);
|
||||
CheckCdkResponseForError(socket, soapResponseVehicleInsertUpdate);
|
||||
const VehicleFromDMS = result && result.return && result.return.vehicle;
|
||||
const VehicleFromDMS = result?.return && result.return.vehicle;
|
||||
return VehicleFromDMS;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in QueryDmsVehicleById - ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in QueryDmsVehicleById - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -237,28 +249,23 @@ async function QueryDmsCustomerById(socket, JobData, CustomerId) {
|
||||
|
||||
const [result, rawResponse, , rawRequest] = soapResponseCustomerInsertUpdate;
|
||||
|
||||
CdkBase.createXmlEvent(socket, rawRequest, `soapClientCustomerInsertUpdate.readAsync request.`);
|
||||
WsLogger.createXmlEvent(socket, rawRequest, `soapClientCustomerInsertUpdate.readAsync request.`);
|
||||
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientCustomerInsertUpdate.readAsync response.`);
|
||||
CdkBase.createLogEvent(
|
||||
WsLogger.createXmlEvent(socket, rawResponse, `soapClientCustomerInsertUpdate.readAsync response.`);
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"SILLY",
|
||||
`soapClientCustomerInsertUpdate.readAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate);
|
||||
const CustomersFromDms = result && result.return && result.return.customerParty;
|
||||
const CustomersFromDms = result?.return && result.return.customerParty;
|
||||
return CustomersFromDms;
|
||||
} catch (error) {
|
||||
CdkBase.createXmlEvent(socket, error.request, `soapClientCustomerInsertUpdate.readAsync request.`, true);
|
||||
WsLogger.createXmlEvent(socket, error.request, `soapClientCustomerInsertUpdate.readAsync request.`, true);
|
||||
|
||||
CdkBase.createXmlEvent(
|
||||
socket,
|
||||
error.response && error.response.data,
|
||||
`soapClientCustomerInsertUpdate.readAsync response.`,
|
||||
true
|
||||
);
|
||||
WsLogger.createXmlEvent(socket, error.response?.data, `soapClientCustomerInsertUpdate.readAsync response.`, true);
|
||||
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in QueryDmsCustomerById - ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in QueryDmsCustomerById - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -270,7 +277,7 @@ async function QueryDmsCustomerByName(socket, JobData) {
|
||||
: `${JobData.ownr_ln},${JobData.ownr_fn}`
|
||||
).replace(replaceSpecialRegex, "");
|
||||
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `Begin Query DMS Customer by Name using: ${ownerName}`);
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `Begin Query DMS Customer by Name using: ${ownerName}`);
|
||||
|
||||
try {
|
||||
const soapClientCustomerSearch = await soap.createClientAsync(CdkWsdl.CustomerSearch);
|
||||
@@ -285,29 +292,29 @@ async function QueryDmsCustomerByName(socket, JobData) {
|
||||
|
||||
const [result, rawResponse, , rawRequest] = soapResponseCustomerSearch;
|
||||
|
||||
CdkBase.createXmlEvent(socket, rawRequest, `soapClientCustomerSearch.executeSearchBulkAsync request.`);
|
||||
WsLogger.createXmlEvent(socket, rawRequest, `soapClientCustomerSearch.executeSearchBulkAsync request.`);
|
||||
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientCustomerSearch.executeSearchBulkAsync response.`);
|
||||
WsLogger.createXmlEvent(socket, rawResponse, `soapClientCustomerSearch.executeSearchBulkAsync response.`);
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"SILLY",
|
||||
`soapClientCustomerSearch.executeSearchBulkAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CheckCdkResponseForError(socket, soapResponseCustomerSearch);
|
||||
const CustomersFromDms = (result && result.return) || [];
|
||||
const CustomersFromDms = result?.return || [];
|
||||
return CustomersFromDms;
|
||||
} catch (error) {
|
||||
CdkBase.createXmlEvent(socket, error.request, `soapClientCustomerSearch.executeSearchBulkAsync request.`, true);
|
||||
WsLogger.createXmlEvent(socket, error.request, `soapClientCustomerSearch.executeSearchBulkAsync request.`, true);
|
||||
|
||||
CdkBase.createXmlEvent(
|
||||
WsLogger.createXmlEvent(
|
||||
socket,
|
||||
error.response && error.response.data,
|
||||
error.response?.data,
|
||||
`soapClientCustomerSearch.executeSearchBulkAsync response.`,
|
||||
true
|
||||
);
|
||||
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in QueryDmsCustomerByName - ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in QueryDmsCustomerByName - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -318,7 +325,8 @@ async function GenerateDmsCustomerNumber(socket) {
|
||||
const soapResponseCustomerInsertUpdate = await soapClientCustomerInsertUpdate.getCustomerNumberAsync(
|
||||
{
|
||||
arg0: CDK_CREDENTIALS,
|
||||
arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid }, //TODO: Verify why this does not follow the other standards.
|
||||
//TODO: Verify why this does not follow the other standards.
|
||||
arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid },
|
||||
arg2: { userId: null }
|
||||
},
|
||||
|
||||
@@ -327,33 +335,33 @@ async function GenerateDmsCustomerNumber(socket) {
|
||||
|
||||
const [result, rawResponse, , rawRequest] = soapResponseCustomerInsertUpdate;
|
||||
|
||||
CdkBase.createXmlEvent(socket, rawRequest, `soapClientCustomerInsertUpdate.getCustomerNumberAsync request.`);
|
||||
WsLogger.createXmlEvent(socket, rawRequest, `soapClientCustomerInsertUpdate.getCustomerNumberAsync request.`);
|
||||
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientCustomerInsertUpdate.getCustomerNumberAsync response.`);
|
||||
WsLogger.createXmlEvent(socket, rawResponse, `soapClientCustomerInsertUpdate.getCustomerNumberAsync response.`);
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"SILLY",
|
||||
`soapClientCustomerInsertUpdate.getCustomerNumberAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate);
|
||||
const customerNumber = result && result.return && result.return.customerNumber;
|
||||
const customerNumber = result?.return && result.return.customerNumber;
|
||||
return customerNumber;
|
||||
} catch (error) {
|
||||
CdkBase.createXmlEvent(
|
||||
WsLogger.createXmlEvent(
|
||||
socket,
|
||||
error.request,
|
||||
`soapClientCustomerInsertUpdate.getCustomerNumberAsync request.`,
|
||||
true
|
||||
);
|
||||
|
||||
CdkBase.createXmlEvent(
|
||||
WsLogger.createXmlEvent(
|
||||
socket,
|
||||
error.response && error.response.data,
|
||||
error.response?.data,
|
||||
`soapClientCustomerInsertUpdate.getCustomerNumberAsync response.`,
|
||||
true
|
||||
);
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in GenerateDmsCustomerNumber - ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in GenerateDmsCustomerNumber - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -416,27 +424,22 @@ async function InsertDmsCustomer(socket, newCustomerNumber) {
|
||||
);
|
||||
|
||||
const [result, rawResponse, , rawRequest] = soapResponseCustomerInsertUpdate;
|
||||
CdkBase.createXmlEvent(socket, rawRequest, `soapClientCustomerInsertUpdate.insertAsync request.`);
|
||||
WsLogger.createXmlEvent(socket, rawRequest, `soapClientCustomerInsertUpdate.insertAsync request.`);
|
||||
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientCustomerInsertUpdate.insertAsync response.`);
|
||||
CdkBase.createLogEvent(
|
||||
WsLogger.createXmlEvent(socket, rawResponse, `soapClientCustomerInsertUpdate.insertAsync response.`);
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"SILLY",
|
||||
`soapClientCustomerInsertUpdate.insertAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate);
|
||||
const customer = result && result.return && result.return.customerParty;
|
||||
const customer = result?.return && result.return.customerParty;
|
||||
return customer;
|
||||
} catch (error) {
|
||||
CdkBase.createXmlEvent(socket, error.request, `soapClientCustomerInsertUpdate.insertAsync request.`, true);
|
||||
WsLogger.createXmlEvent(socket, error.request, `soapClientCustomerInsertUpdate.insertAsync request.`, true);
|
||||
|
||||
CdkBase.createXmlEvent(
|
||||
socket,
|
||||
error.response && error.response.data,
|
||||
`soapClientCustomerInsertUpdate.insertAsync response.`,
|
||||
true
|
||||
);
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in InsertDmsCustomer - ${error}`);
|
||||
WsLogger.createXmlEvent(socket, error.response?.data, `soapClientCustomerInsertUpdate.insertAsync response.`, true);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in InsertDmsCustomer - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -497,19 +500,19 @@ async function InsertDmsVehicle(socket) {
|
||||
|
||||
const [result, rawResponse, , rawRequest] = soapResponseVehicleInsertUpdate;
|
||||
|
||||
CdkBase.createXmlEvent(socket, rawRequest, `soapClientVehicleInsertUpdate.insertAsync request.`);
|
||||
WsLogger.createXmlEvent(socket, rawRequest, `soapClientVehicleInsertUpdate.insertAsync request.`);
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"SILLY",
|
||||
`soapClientVehicleInsertUpdate.insertAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientVehicleInsertUpdate.insertAsync response.`);
|
||||
WsLogger.createXmlEvent(socket, rawResponse, `soapClientVehicleInsertUpdate.insertAsync response.`);
|
||||
CheckCdkResponseForError(socket, soapResponseVehicleInsertUpdate);
|
||||
const VehicleFromDMS = result && result.return && result.return.vehicle;
|
||||
const VehicleFromDMS = result?.return && result.return.vehicle;
|
||||
return VehicleFromDMS;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in InsertDmsVehicle - ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in InsertDmsVehicle - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -523,12 +526,10 @@ async function UpdateDmsVehicle(socket) {
|
||||
//if it's a generic customer, don't update the vehicle owners.
|
||||
|
||||
if (socket.selectedCustomerId === socket.JobData.bodyshop.cdk_configuration.generic_customer_number) {
|
||||
ids = socket.DMSVeh && socket.DMSVeh.owners && socket.DMSVeh.owners;
|
||||
ids = socket.DMSVeh?.owners && socket.DMSVeh.owners;
|
||||
} else {
|
||||
const existingOwnerinVeh =
|
||||
socket.DMSVeh &&
|
||||
socket.DMSVeh.owners &&
|
||||
socket.DMSVeh.owners.find((o) => o.id.value === socket.DMSCust.id.value);
|
||||
socket.DMSVeh?.owners && socket.DMSVeh.owners.find((o) => o.id.value === socket.DMSCust.id.value);
|
||||
|
||||
if (existingOwnerinVeh) {
|
||||
ids = socket.DMSVeh.owners.map((o) => {
|
||||
@@ -540,10 +541,7 @@ async function UpdateDmsVehicle(socket) {
|
||||
};
|
||||
});
|
||||
} else {
|
||||
const oldOwner =
|
||||
socket.DMSVeh &&
|
||||
socket.DMSVeh.owners &&
|
||||
socket.DMSVeh.owners.find((o) => o.id.assigningPartyId === "CURRENT");
|
||||
const oldOwner = socket.DMSVeh?.owners && socket.DMSVeh.owners.find((o) => o.id.assigningPartyId === "CURRENT");
|
||||
|
||||
ids = [
|
||||
{
|
||||
@@ -603,19 +601,19 @@ async function UpdateDmsVehicle(socket) {
|
||||
});
|
||||
const [result, rawResponse, , rawRequest] = soapResponseVehicleInsertUpdate;
|
||||
|
||||
CdkBase.createXmlEvent(socket, rawRequest, `soapClientVehicleInsertUpdate.updateAsync request.`);
|
||||
WsLogger.createXmlEvent(socket, rawRequest, `soapClientVehicleInsertUpdate.updateAsync request.`);
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`soapClientVehicleInsertUpdate.updateAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientVehicleInsertUpdate.updateAsync response.`);
|
||||
WsLogger.createXmlEvent(socket, rawResponse, `soapClientVehicleInsertUpdate.updateAsync response.`);
|
||||
CheckCdkResponseForError(socket, soapResponseVehicleInsertUpdate);
|
||||
const VehicleFromDMS = result && result.return && result.return.vehicle;
|
||||
const VehicleFromDMS = result?.return && result.return.vehicle;
|
||||
return VehicleFromDMS;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in UpdateDmsVehicle - ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in UpdateDmsVehicle - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -642,18 +640,18 @@ async function InsertServiceVehicleHistory(socket) {
|
||||
|
||||
const [result, rawResponse, , rawRequest] = soapResponseServiceHistoryInsert;
|
||||
|
||||
CdkBase.createXmlEvent(socket, rawRequest, `soapClientServiceHistoryInsert.serviceHistoryHeaderInsert request.`);
|
||||
WsLogger.createXmlEvent(socket, rawRequest, `soapClientServiceHistoryInsert.serviceHistoryHeaderInsert request.`);
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"SILLY",
|
||||
`soapClientServiceHistoryInsert.serviceHistoryHeaderInsert Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientServiceHistoryInsert.serviceHistoryHeaderInsert response.`);
|
||||
WsLogger.createXmlEvent(socket, rawResponse, `soapClientServiceHistoryInsert.serviceHistoryHeaderInsert response.`);
|
||||
CheckCdkResponseForError(socket, soapResponseServiceHistoryInsert);
|
||||
return result && result.return;
|
||||
return result?.return;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in InsertServiceVehicleHistory - ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in InsertServiceVehicleHistory - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -669,8 +667,10 @@ async function InsertDmsStartWip(socket) {
|
||||
acctgDate: moment().tz(socket.JobData.bodyshop.timezone).format("YYYY-MM-DD"),
|
||||
//socket.JobData.invoice_date
|
||||
desc: socket.txEnvelope.story && socket.txEnvelope.story.replace(replaceSpecialRegex, ""),
|
||||
docType: 10 || 7, //Need to check what this usually would be? Apparently it is almost always 10 or 7.
|
||||
//1 Cash Receipt , 2 Check, 3 Journal Voucher, 4 Parts invoice, 5 Payable Invoice, 6 Recurring Entry, 7 Repair Order Invoice, 8 Vehicle Purchase Invoice, 9 Vehicle Sale Invoice, 10 Other, 11 Payroll, 12 Finance Charge, 13 FMLR Invoice, 14 Parts Credit Memo, 15 Manufacturer Document, 16 FMLR Credit Memo
|
||||
docType: 10, // Need to check what this usually would be? Apparently it is almost always 10 or 7.
|
||||
//1 Cash Receipt , 2 Check, 3 Journal Voucher, 4 Parts invoice, 5 Payable Invoice, 6 Recurring Entry, 7 Repair
|
||||
// Order Invoice, 8 Vehicle Purchase Invoice, 9 Vehicle Sale Invoice, 10 Other, 11 Payroll, 12 Finance Charge,
|
||||
// 13 FMLR Invoice, 14 Parts Credit Memo, 15 Manufacturer Document, 16 FMLR Credit Memo
|
||||
m13Flag: 0,
|
||||
refer: socket.JobData.ro_number,
|
||||
srcCo: socket.JobData.bodyshop.cdk_configuration.srcco,
|
||||
@@ -682,19 +682,19 @@ async function InsertDmsStartWip(socket) {
|
||||
|
||||
const [result, rawResponse, , rawRequest] = soapResponseAccountingGLInsertUpdate;
|
||||
|
||||
CdkBase.createXmlEvent(socket, rawRequest, `soapClientAccountingGLInsertUpdate.doStartWIPAsync request.`);
|
||||
WsLogger.createXmlEvent(socket, rawRequest, `soapClientAccountingGLInsertUpdate.doStartWIPAsync request.`);
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"SILLY",
|
||||
`soapClientAccountingGLInsertUpdate.doStartWIPAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doStartWIPAsync response.`);
|
||||
WsLogger.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doStartWIPAsync response.`);
|
||||
CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate);
|
||||
const TransactionHeader = result && result.return;
|
||||
const TransactionHeader = result?.return;
|
||||
return TransactionHeader;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in InsertDmsStartWip - ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in InsertDmsStartWip - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -713,19 +713,19 @@ async function InsertDmsBatchWip(socket) {
|
||||
|
||||
const [result, rawResponse, , rawRequest] = soapResponseAccountingGLInsertUpdate;
|
||||
|
||||
CdkBase.createXmlEvent(socket, rawRequest, `soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync request.`);
|
||||
WsLogger.createXmlEvent(socket, rawRequest, `soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync request.`);
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"SILLY",
|
||||
`soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync response.`);
|
||||
WsLogger.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync response.`);
|
||||
CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate);
|
||||
const BatchWipResult = result && result.return;
|
||||
const BatchWipResult = result?.return;
|
||||
return BatchWipResult;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in InsertDmsBatchWip - ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in InsertDmsBatchWip - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -877,19 +877,19 @@ async function PostDmsBatchWip(socket) {
|
||||
|
||||
const [result, rawResponse, , rawRequest] = soapResponseAccountingGLInsertUpdate;
|
||||
|
||||
CdkBase.createXmlEvent(socket, rawRequest, `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync request.`);
|
||||
WsLogger.createXmlEvent(socket, rawRequest, `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync request.`);
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"SILLY",
|
||||
`soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync response.`);
|
||||
WsLogger.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync response.`);
|
||||
// CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate);
|
||||
const PostResult = result && result.return;
|
||||
const PostResult = result?.return;
|
||||
return PostResult;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in PostDmsBatchWip - ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in PostDmsBatchWip - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -906,19 +906,19 @@ async function QueryDmsErrWip(socket) {
|
||||
|
||||
const [result, rawResponse, , rawRequest] = soapResponseAccountingGLInsertUpdate;
|
||||
|
||||
CdkBase.createXmlEvent(socket, rawRequest, `soapClientAccountingGLInsertUpdate.doErrWIPAsync request.`);
|
||||
WsLogger.createXmlEvent(socket, rawRequest, `soapClientAccountingGLInsertUpdate.doErrWIPAsync request.`);
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`soapClientAccountingGLInsertUpdate.doErrWIPAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doErrWIPAsync response.`);
|
||||
WsLogger.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doErrWIPAsync response.`);
|
||||
CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate);
|
||||
const PostResult = result && result.return;
|
||||
const PostResult = result?.return;
|
||||
return PostResult;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in QueryDmsErrWip - ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in QueryDmsErrWip - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -937,28 +937,31 @@ async function DeleteDmsWip(socket) {
|
||||
|
||||
const [result, rawResponse, , rawRequest] = soapResponseAccountingGLInsertUpdate;
|
||||
|
||||
CdkBase.createXmlEvent(socket, rawRequest, `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync request.`);
|
||||
WsLogger.createXmlEvent(socket, rawRequest, `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync request.`);
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"SILLY",
|
||||
`soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync response.`);
|
||||
WsLogger.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync response.`);
|
||||
CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate);
|
||||
const PostResult = result && result.return;
|
||||
const PostResult = result?.return;
|
||||
return PostResult;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in PostDmsBatchWip - ${error}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in PostDmsBatchWip - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function MarkJobExported(socket, jobid) {
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `Marking job as exported for id ${jobid}`);
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `Marking job as exported for id ${jobid}`);
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
|
||||
const currentToken =
|
||||
(socket?.data && socket.data.authToken) || (socket?.handshake?.auth && socket.handshake.auth.token);
|
||||
|
||||
const result = await client
|
||||
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
|
||||
.setHeaders({ Authorization: `Bearer ${currentToken}` })
|
||||
.request(queries.MARK_JOB_EXPORTED, {
|
||||
jobId: jobid,
|
||||
job: {
|
||||
@@ -984,20 +987,23 @@ async function MarkJobExported(socket, jobid) {
|
||||
async function InsertFailedExportLog(socket, error) {
|
||||
try {
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
|
||||
const currentToken =
|
||||
(socket?.data && socket.data.authToken) || (socket?.handshake?.auth && socket.handshake.auth.token);
|
||||
|
||||
const result = await client
|
||||
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
|
||||
.setHeaders({ Authorization: `Bearer ${currentToken}` })
|
||||
.request(queries.INSERT_EXPORT_LOG, {
|
||||
log: {
|
||||
logs: [{
|
||||
bodyshopid: socket.JobData.bodyshop.id,
|
||||
jobid: socket.JobData.id,
|
||||
successful: false,
|
||||
message: JSON.stringify(error),
|
||||
useremail: socket.user.email
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (error2) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in InsertFailedExportLog - ${error} - ${JSON.stringify(error2)}`);
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in InsertFailedExportLog - ${error} - ${JSON.stringify(error2)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,46 +117,44 @@ async function processShopData(shopsToProcess, start, end, skipUpload, ignoreDat
|
||||
imexshopid: shopid,
|
||||
json: JSON.stringify(carfaxObject, null, 2),
|
||||
filename: `${shopid}_${moment().format("DDMMYYYY_HHMMss")}.json`,
|
||||
count: carfaxObject?.job?.length || 0
|
||||
count: carfaxObject.job.length
|
||||
};
|
||||
|
||||
if (skipUpload) {
|
||||
fs.writeFileSync(`./logs/${jsonObj.filename}`, jsonObj.json);
|
||||
uploadToS3(jsonObj, S3_BUCKET_NAME);
|
||||
} else {
|
||||
if (jsonObj.count > 0) {
|
||||
await uploadViaSFTP(jsonObj);
|
||||
await uploadViaSFTP(jsonObj);
|
||||
|
||||
await sendMexicoBillingEmail({
|
||||
subject: `${shopid.replace(/_/g, "").toUpperCase()}_MexicoRPS_${moment().format("MMDDYYYY")} ROs ${jsonObj.count} Error ${errorCode(jsonObj)}`,
|
||||
text: `Errors:\n${JSON.stringify(
|
||||
erroredJobs.map((ej) => ({
|
||||
jobid: ej.job?.id,
|
||||
error: ej.error
|
||||
})),
|
||||
null,
|
||||
2
|
||||
)}\n\nUploaded:\n${JSON.stringify(
|
||||
{
|
||||
bodyshopid: bodyshop.id,
|
||||
imexshopid: shopid,
|
||||
count: jsonObj.count,
|
||||
filename: jsonObj.filename,
|
||||
result: jsonObj.result
|
||||
},
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
});
|
||||
}
|
||||
await sendMexicoBillingEmail({
|
||||
subject: `${shopid.replace(/_/g, "").toUpperCase()}_MexicoRPS_${moment().format("MMDDYYYY")} ROs ${jsonObj.count} Error ${errorCode(jsonObj)}`,
|
||||
text: `Errors:\n${JSON.stringify(
|
||||
erroredJobs.map((ej) => ({
|
||||
jobid: ej.job?.id,
|
||||
error: ej.error
|
||||
})),
|
||||
null,
|
||||
2
|
||||
)}\n\nUploaded:\n${JSON.stringify(
|
||||
{
|
||||
bodyshopid: bodyshop.id,
|
||||
imexshopid: shopid,
|
||||
count: jsonObj.count,
|
||||
filename: jsonObj.filename,
|
||||
result: jsonObj.result
|
||||
},
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
});
|
||||
}
|
||||
|
||||
jsonObj.count > 0 && allJSONResults.push({
|
||||
allJSONResults.push({
|
||||
bodyshopid: bodyshop.id,
|
||||
imexshopid: shopid,
|
||||
count: jsonObj.count,
|
||||
filename: jsonObj.filename,
|
||||
result: jsonObj.result || "No Upload Result Available"
|
||||
result: jsonObj.result
|
||||
});
|
||||
|
||||
logger.log("CARFAX-RPS-end-shop-extract", "DEBUG", "api", bodyshop.id, {
|
||||
@@ -236,10 +234,11 @@ const CreateRepairOrderTag = (job, errorCallback) => {
|
||||
const ret = {
|
||||
ro_number: crypto.createHash("md5").update(job.id, "utf8").digest("hex"),
|
||||
v_vin: job.v_vin || "",
|
||||
v_year: (() => {
|
||||
const y = parseInt(job.v_model_yr);
|
||||
return isNaN(y) ? null : y < 100 ? y + (y >= (new Date().getFullYear() + 1) % 100 ? 1900 : 2000) : y;
|
||||
})(),
|
||||
v_year: job.v_model_yr
|
||||
? parseInt(job.v_model_yr.match(/\d/g))
|
||||
? parseInt(job.v_model_yr.match(/\d/g).join(""), 10)
|
||||
: ""
|
||||
: "",
|
||||
v_make: job.v_makedesc || "",
|
||||
v_model: job.v_model || "",
|
||||
|
||||
|
||||
@@ -160,42 +160,40 @@ async function processShopData(shopsToProcess, start, end, skipUpload, ignoreDat
|
||||
imexshopid: shopid,
|
||||
json: JSON.stringify(carfaxObject, null, 2),
|
||||
filename: `${shopid}_${moment().format("DDMMYYYY_HHMMss")}.json`,
|
||||
count: carfaxObject?.job?.length || 0
|
||||
count: carfaxObject.job.length
|
||||
};
|
||||
|
||||
if (skipUpload) {
|
||||
fs.writeFileSync(`./logs/${jsonObj.filename}`, jsonObj.json);
|
||||
uploadToS3(jsonObj);
|
||||
} else {
|
||||
if (jsonObj.count > 0) {
|
||||
await uploadViaSFTP(jsonObj);
|
||||
await uploadViaSFTP(jsonObj);
|
||||
|
||||
await sendMexicoBillingEmail({
|
||||
subject: `${shopid.replace(/_/g, "").toUpperCase()}_Mexico${InstanceManager({
|
||||
imex: "IO",
|
||||
rome: "RO"
|
||||
})}_${moment().format("MMDDYYYY")} ROs ${jsonObj.count} Error ${errorCode(jsonObj)}`,
|
||||
text: `Errors:\n${JSON.stringify(
|
||||
erroredJobs.map((ej) => ({
|
||||
ro_number: ej.job?.ro_number,
|
||||
jobid: ej.job?.id,
|
||||
error: ej.error
|
||||
})),
|
||||
null,
|
||||
2
|
||||
)}\n\nUploaded:\n${JSON.stringify(
|
||||
{
|
||||
bodyshopid: bodyshop.id,
|
||||
imexshopid: shopid,
|
||||
count: jsonObj.count,
|
||||
filename: jsonObj.filename,
|
||||
result: jsonObj.result
|
||||
},
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
});
|
||||
}
|
||||
await sendMexicoBillingEmail({
|
||||
subject: `${shopid.replace(/_/g, "").toUpperCase()}_Mexico${InstanceManager({
|
||||
imex: "IO",
|
||||
rome: "RO"
|
||||
})}_${moment().format("MMDDYYYY")} ROs ${jsonObj.count} Error ${errorCode(jsonObj)}`,
|
||||
text: `Errors:\n${JSON.stringify(
|
||||
erroredJobs.map((ej) => ({
|
||||
ro_number: ej.job?.ro_number,
|
||||
jobid: ej.job?.id,
|
||||
error: ej.error
|
||||
})),
|
||||
null,
|
||||
2
|
||||
)}\n\nUploaded:\n${JSON.stringify(
|
||||
{
|
||||
bodyshopid: bodyshop.id,
|
||||
imexshopid: shopid,
|
||||
count: jsonObj.count,
|
||||
filename: jsonObj.filename,
|
||||
result: jsonObj.result
|
||||
},
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
});
|
||||
}
|
||||
|
||||
allJSONResults.push({
|
||||
@@ -203,7 +201,7 @@ async function processShopData(shopsToProcess, start, end, skipUpload, ignoreDat
|
||||
imexshopid: shopid,
|
||||
count: jsonObj.count,
|
||||
filename: jsonObj.filename,
|
||||
result: jsonObj.result || "No Upload Result Available"
|
||||
result: jsonObj.result
|
||||
});
|
||||
|
||||
logger.log("CARFAX-end-shop-extract", "DEBUG", "api", bodyshop.id, {
|
||||
@@ -288,10 +286,11 @@ const CreateRepairOrderTag = (job, errorCallback) => {
|
||||
const ret = {
|
||||
ro_number: crypto.createHash("md5").update(job.ro_number, "utf8").digest("hex"),
|
||||
v_vin: job.v_vin || "",
|
||||
v_year: (() => {
|
||||
const y = parseInt(job.v_model_yr);
|
||||
return isNaN(y) ? null : y < 100 ? y + (y >= (new Date().getFullYear() + 1) % 100 ? 1900 : 2000) : y;
|
||||
})(),
|
||||
v_year: job.v_model_yr
|
||||
? parseInt(job.v_model_yr.match(/\d/g))
|
||||
? parseInt(job.v_model_yr.match(/\d/g).join(""), 10)
|
||||
: ""
|
||||
: "",
|
||||
v_make: job.v_make_desc || "",
|
||||
v_model: job.v_model_desc || "",
|
||||
|
||||
|
||||
@@ -219,6 +219,8 @@ const CreateRepairOrderTag = (job, errorCallback) => {
|
||||
}
|
||||
|
||||
const repairCosts = CreateCosts(job);
|
||||
const jobline = CreateJobLines(job.joblines);
|
||||
const timeticket = CreateTimeTickets(job.timetickets);
|
||||
|
||||
try {
|
||||
const ret = {
|
||||
@@ -288,100 +290,8 @@ const CreateRepairOrderTag = (job, errorCallback) => {
|
||||
(job.date_exported && moment(job.date_exported).tz(job.bodyshop.timezone).format(DateFormat)) || "",
|
||||
DateVoid: (job.date_void && moment(job.date_void).tz(job.bodyshop.timezone).format(DateFormat)) || ""
|
||||
},
|
||||
JobLineDetails: (function () {
|
||||
const joblineSource = Array.isArray(job.joblines) ? job.joblines : job.joblines ? [job.joblines] : [];
|
||||
if (joblineSource.length === 0) return { jobline: [] };
|
||||
return {
|
||||
jobline: joblineSource.map((jl = {}) => ({
|
||||
line_description: jl.line_desc || jl.line_description || "",
|
||||
oem_part_no: jl.oem_partno || jl.oem_part_no || "",
|
||||
alt_part_no: jl.alt_partno || jl.alt_part_no || "",
|
||||
op_code_desc: jl.op_code_desc || "",
|
||||
part_type: jl.part_type || "",
|
||||
part_qty: jl.part_qty ?? jl.quantity ?? 0,
|
||||
part_price: jl.act_price ?? jl.part_price ?? 0,
|
||||
labor_type: jl.mod_lbr_ty || jl.labor_type || "",
|
||||
labor_hours: jl.mod_lb_hrs ?? jl.labor_hours ?? 0,
|
||||
labor_sale: jl.lbr_amt ?? jl.labor_sale ?? 0
|
||||
}))
|
||||
};
|
||||
})(),
|
||||
BillsDetails: (function () {
|
||||
const billsSource = Array.isArray(job.bills) ? job.bills : job.bills ? [job.bills] : [];
|
||||
if (billsSource.length === 0) return { BillDetails: [] };
|
||||
return {
|
||||
BillDetails: billsSource.map(
|
||||
({
|
||||
billlines = [],
|
||||
date = "",
|
||||
is_credit_memo = false,
|
||||
invoice_number = "",
|
||||
isinhouse = false,
|
||||
vendor = {}
|
||||
} = {}) => ({
|
||||
BillLines: {
|
||||
BillLine: billlines.map((bl = {}) => ({
|
||||
line_description: bl.line_desc || bl.line_description || "",
|
||||
part_price: bl.actual_price ?? bl.part_price ?? bl.act_price ?? 0,
|
||||
actual_cost: bl.actual_cost ?? 0,
|
||||
cost_center: bl.cost_center || "",
|
||||
deductedfromlbr: bl.deductedfromlbr || false,
|
||||
part_qty: bl.quantity ?? bl.part_qty ?? 0,
|
||||
oem_part_no: bl.oem_partno || bl.oem_part_no || "",
|
||||
alt_part_no: bl.alt_partno || bl.alt_part_no || ""
|
||||
}))
|
||||
},
|
||||
date,
|
||||
is_credit_memo,
|
||||
invoice_number,
|
||||
isinhouse,
|
||||
vendorName: vendor.name || ""
|
||||
})
|
||||
)
|
||||
};
|
||||
})(),
|
||||
JobNotes: (function () {
|
||||
const notesSource = Array.isArray(job.notes) ? job.notes : job.notes ? [job.notes] : [];
|
||||
if (notesSource.length === 0) return { JobNote: [] };
|
||||
return {
|
||||
JobNote: notesSource.map((note = {}) => ({
|
||||
created_at: note.created_at || "",
|
||||
created_by: note.created_by || "",
|
||||
critical: note.critical || false,
|
||||
private: note.private || false,
|
||||
text: note.text || "",
|
||||
type: note.type || ""
|
||||
}))
|
||||
};
|
||||
})(),
|
||||
TimeTicketDetails: (function () {
|
||||
const ticketSource = Array.isArray(job.timetickets)
|
||||
? job.timetickets
|
||||
: job.timetickets
|
||||
? [job.timetickets]
|
||||
: [];
|
||||
if (ticketSource.length === 0) return { timeticket: [] };
|
||||
return {
|
||||
timeticket: ticketSource.map((ticket = {}) => ({
|
||||
date: ticket.date || "",
|
||||
employee:
|
||||
ticket.employee && ticket.employee.employee_number
|
||||
? ticket.employee.employee_number
|
||||
.trim()
|
||||
.concat(" - ", ticket.employee.first_name.trim(), " ", ticket.employee.last_name.trim())
|
||||
.trim()
|
||||
: "",
|
||||
productive_hrs: ticket.productivehrs ?? 0,
|
||||
actual_hrs: ticket.actualhrs ?? 0,
|
||||
cost_center: ticket.cost_center || "",
|
||||
flat_rate: ticket.flat_rate || false,
|
||||
rate: ticket.rate ?? 0,
|
||||
ticket_cost: ticket.flat_rate
|
||||
? ticket.rate * (ticket.productivehrs || 0)
|
||||
: ticket.rate * (ticket.actualhrs || 0)
|
||||
}))
|
||||
};
|
||||
})(),
|
||||
JobLineDetails: { jobline },
|
||||
TimeTicketDetails: { timeticket },
|
||||
Sales: {
|
||||
Labour: {
|
||||
Aluminum: Dinero(job.job_totals.rates.laa.total).toFormat(DineroFormat),
|
||||
@@ -726,3 +636,42 @@ const CreateCosts = (job) => {
|
||||
}, 0)
|
||||
};
|
||||
};
|
||||
|
||||
const CreateJobLines = (joblines) => {
|
||||
const repairLines = [];
|
||||
joblines.forEach((jobline) => {
|
||||
repairLines.push({
|
||||
line_description: jobline.line_desc,
|
||||
oem_part_no: jobline.oem_partno,
|
||||
alt_part_no: jobline.alt_partno,
|
||||
op_code_desc: jobline.op_code_desc,
|
||||
part_type: jobline.part_type,
|
||||
part_qty: jobline.part_qty,
|
||||
part_price: jobline.act_price,
|
||||
labor_type: jobline.mod_lbr_ty,
|
||||
labor_hours: jobline.mod_lb_hrs,
|
||||
labor_sale: jobline.lbr_amt
|
||||
});
|
||||
});
|
||||
return repairLines;
|
||||
};
|
||||
|
||||
const CreateTimeTickets = (timetickets) => {
|
||||
const timeTickets = [];
|
||||
timetickets.forEach((ticket) => {
|
||||
timeTickets.push({
|
||||
date: ticket.date,
|
||||
employee: ticket.employee.employee_number
|
||||
.trim()
|
||||
.concat(" - ", ticket.employee.first_name.trim(), " ", ticket.employee.last_name.trim())
|
||||
.trim(),
|
||||
productive_hrs: ticket.productivehrs,
|
||||
actual_hrs: ticket.actualhrs,
|
||||
cost_center: ticket.cost_center,
|
||||
flat_rate: ticket.flat_rate,
|
||||
rate: ticket.rate,
|
||||
ticket_cost: ticket.flat_rate ? ticket.rate * ticket.productive_hrs : ticket.rate * ticket.actual_hrs
|
||||
});
|
||||
});
|
||||
return timeTickets;
|
||||
};
|
||||
|
||||
@@ -55,9 +55,7 @@ exports.default = async (req, res) => {
|
||||
"patrick.fic@convenient-brands.com",
|
||||
"bradley.rhoades@convenient-brands.com",
|
||||
"jrome@rometech.com",
|
||||
"ivana@imexsystems.ca",
|
||||
"support@imexsystems.ca",
|
||||
"sarah@rometech.com"
|
||||
"ivana@imexsystems.ca"
|
||||
],
|
||||
subject: `RO Usage Report - ${moment().format("MM/DD/YYYY")}`,
|
||||
text: `
|
||||
|
||||
460
server/fortellis/fortellis-helpers.js
Normal file
460
server/fortellis/fortellis-helpers.js
Normal file
@@ -0,0 +1,460 @@
|
||||
const path = require("path");
|
||||
require("dotenv").config({
|
||||
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
|
||||
});
|
||||
|
||||
// const CalcualteAllocations = require("../cdk/cdk-calculate-allocations").default;
|
||||
const CreateFortellisLogEvent = require("./fortellis-logger");
|
||||
const logger = require("../utils/logger");
|
||||
const uuid = require("uuid").v4;
|
||||
const AxiosLib = require("axios").default;
|
||||
const axios = AxiosLib.create();
|
||||
const axiosCurlirize = require("axios-curlirize").default;
|
||||
|
||||
// Custom error class for Fortellis API errors
|
||||
class FortellisApiError extends Error {
|
||||
constructor(message, details) {
|
||||
super(message);
|
||||
this.name = "FortellisApiError";
|
||||
this.reqId = details.reqId;
|
||||
this.url = details.url;
|
||||
this.apiName = details.apiName;
|
||||
this.errorData = details.errorData;
|
||||
this.errorStatus = details.errorStatus;
|
||||
this.errorStatusText = details.errorStatusText;
|
||||
this.originalError = details.originalError;
|
||||
}
|
||||
}
|
||||
|
||||
axiosCurlirize(axios, (_result, _err) => {
|
||||
//Left intentionally blank. We don't want to console.log. We handle logging the cURL in MakeFortellisCall once completed.
|
||||
});
|
||||
|
||||
const getTransactionType = (jobid) => `fortellis:${jobid}`;
|
||||
const defaultFortellisTTL = 60 * 60;
|
||||
|
||||
async function GetAuthToken() {
|
||||
//Done with Authorization Code Flow
|
||||
//https://docs.fortellis.io/docs/tutorials/solution-integration/authorization-code-flow/
|
||||
|
||||
//TODO: This should get stored in the redis cache and only be refreshed when it expires.
|
||||
const {
|
||||
data: { access_token, expires_in, token_type }
|
||||
} = await axios.post(
|
||||
process.env.FORTELLIS_AUTH_URL,
|
||||
{},
|
||||
{
|
||||
auth: {
|
||||
username: process.env.FORTELLIS_KEY,
|
||||
password: process.env.FORTELLIS_SECRET
|
||||
},
|
||||
params: {
|
||||
grant_type: "client_credentials",
|
||||
scope: "anonymous"
|
||||
}
|
||||
}
|
||||
);
|
||||
return access_token;
|
||||
}
|
||||
|
||||
async function FetchSubscriptions({ redisHelpers, socket, jobid, SubscriptionObject }) {
|
||||
try {
|
||||
const { setSessionTransactionData, getSessionTransactionData } = redisHelpers;
|
||||
|
||||
//Get Subscription ID from Transaction Envelope
|
||||
const { SubscriptionID } = SubscriptionObject
|
||||
? SubscriptionObject
|
||||
: await getSessionTransactionData(socket.id, getTransactionType(jobid), `txEnvelope`);
|
||||
if (!SubscriptionID) {
|
||||
throw new Error("Subscription ID not found in transaction envelope.");
|
||||
}
|
||||
|
||||
//Check to See if the subscription meta is in the Redis Cache.
|
||||
const SubscriptionMetaFromCache = await getSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(jobid),
|
||||
FortellisCacheEnums.SubscriptionMeta
|
||||
);
|
||||
|
||||
// If it is, return it.
|
||||
if (SubscriptionMetaFromCache) {
|
||||
return SubscriptionMetaFromCache;
|
||||
} else {
|
||||
const access_token = await GetAuthToken();
|
||||
const subscriptions = await axios.get(FortellisActions.GetSubscription.url, {
|
||||
headers: { Authorization: `Bearer ${access_token}` },
|
||||
logRequest: false
|
||||
});
|
||||
const SubscriptionMeta = subscriptions.data.subscriptions.find((s) => s.subscriptionId === SubscriptionID);
|
||||
if (setSessionTransactionData) {
|
||||
await setSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(jobid),
|
||||
FortellisCacheEnums.SubscriptionMeta,
|
||||
SubscriptionMeta,
|
||||
defaultFortellisTTL
|
||||
);
|
||||
}
|
||||
return SubscriptionMeta;
|
||||
}
|
||||
} catch (error) {
|
||||
CreateFortellisLogEvent(socket, "ERROR", `Error fetching subscription metadata.`, {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function GetDepartmentId({ apiName, debug = false, SubscriptionMeta, overrideDepartmentId }) {
|
||||
if (!apiName) throw new Error("apiName not provided. Unable to get department without apiName.");
|
||||
if (debug) {
|
||||
console.log("API Names & Departments ");
|
||||
console.log("===========");
|
||||
console.log(JSON.stringify(SubscriptionMeta.apiDmsInfo, null, 4));
|
||||
console.log("===========");
|
||||
}
|
||||
|
||||
const departmentIds = SubscriptionMeta.apiDmsInfo //Get the subscription object.
|
||||
.find((info) => info.name === apiName)?.departments; //Departments are categorized by API name and have an array of departments.
|
||||
|
||||
if (overrideDepartmentId) {
|
||||
return departmentIds && departmentIds.find(d => d.id === overrideDepartmentId)?.id
|
||||
} else {
|
||||
|
||||
return departmentIds && departmentIds[0] && departmentIds[0].id; //TODO: This makes the assumption that there is only 1 department.
|
||||
}
|
||||
}
|
||||
|
||||
//Highest level function call to make a call to fortellis. This should be the only call required, and it will handle all the logic for making the call.
|
||||
async function MakeFortellisCall({
|
||||
apiName,
|
||||
url,
|
||||
headers = {},
|
||||
body = {},
|
||||
type = "post",
|
||||
debug = false,
|
||||
requestPathParams,
|
||||
requestSearchParams = [], //Array of key/value strings like [["key", "value"]]
|
||||
jobid,
|
||||
redisHelpers,
|
||||
socket,
|
||||
SubscriptionObject, //This is used because of the get make models to bypass all of the redis calls.
|
||||
overrideDepartmentId
|
||||
}) {
|
||||
//const { setSessionTransactionData, getSessionTransactionData } = redisHelpers;
|
||||
|
||||
const fullUrl = constructFullUrl({ url, pathParams: requestPathParams, requestSearchParams });
|
||||
if (debug) console.log(`Executing ${type} to ${fullUrl}`);
|
||||
const ReqId = uuid();
|
||||
const access_token = await GetAuthToken();
|
||||
const SubscriptionMeta = await FetchSubscriptions({ redisHelpers, socket, jobid, SubscriptionObject });
|
||||
const DepartmentId = await GetDepartmentId({ apiName, debug, SubscriptionMeta, overrideDepartmentId });
|
||||
|
||||
if (debug) {
|
||||
console.log(
|
||||
`ReqID: ${ReqId} | SubscriptionID: ${SubscriptionMeta.subscriptionId} | DepartmentId: ${DepartmentId}`
|
||||
);
|
||||
console.log(`Body Contents: ${JSON.stringify(body, null, 4)}`);
|
||||
}
|
||||
|
||||
try {
|
||||
let result;
|
||||
switch (type) {
|
||||
case "post":
|
||||
default:
|
||||
result = await axios.post(fullUrl, body, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${access_token}`,
|
||||
"Subscription-Id": SubscriptionMeta.subscriptionId,
|
||||
"Request-Id": ReqId,
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
...(DepartmentId && { "Department-Id": DepartmentId }),
|
||||
...headers
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "get":
|
||||
result = await axios.get(fullUrl, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${access_token}`,
|
||||
"Subscription-Id": SubscriptionMeta.subscriptionId,
|
||||
"Request-Id": ReqId,
|
||||
Accept: "application/json",
|
||||
"Department-Id": DepartmentId,
|
||||
...headers
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "put":
|
||||
result = await axios.put(fullUrl, body, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${access_token}`,
|
||||
"Subscription-Id": SubscriptionMeta.subscriptionId,
|
||||
"Request-Id": ReqId,
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"Department-Id": DepartmentId,
|
||||
...headers
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
console.log(`ReqID: ${ReqId} Data`);
|
||||
console.log(JSON.stringify(result.data, null, 4));
|
||||
}
|
||||
|
||||
if (result.data.checkStatusAfterSeconds) {
|
||||
return DelayedCallback({
|
||||
delayMeta: result.data,
|
||||
access_token,
|
||||
SubscriptionID: SubscriptionMeta.subscriptionId,
|
||||
ReqId,
|
||||
departmentIds: DepartmentId
|
||||
});
|
||||
}
|
||||
|
||||
logger.log(
|
||||
"fortellis-log-event-json",
|
||||
"DEBUG",
|
||||
socket?.user?.email,
|
||||
jobid,
|
||||
{
|
||||
requestcurl: result.config.curlCommand,
|
||||
reqid: result.config.headers["Request-Id"] || null,
|
||||
subscriptionId: result.config.headers["Subscription-Id"] || null,
|
||||
resultdata: result.data,
|
||||
resultStatus: result.status
|
||||
},
|
||||
);
|
||||
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
const errorDetails = {
|
||||
reqId: ReqId,
|
||||
url: fullUrl,
|
||||
apiName,
|
||||
errorData: error.response?.data,
|
||||
errorStatus: error.response?.status,
|
||||
errorStatusText: error.response?.statusText,
|
||||
originalError: error
|
||||
};
|
||||
|
||||
logger.log(
|
||||
"fortellis-log-event-error",
|
||||
"ERROR",
|
||||
socket?.user?.email,
|
||||
socket?.recordid,
|
||||
{
|
||||
wsmessage: "",//message,
|
||||
curl: error.config.curl.curlCommand,
|
||||
reqid: error.request.headers["Request-Id"] || null,
|
||||
subscriptionId: error.request.headers["Subscription-Id"] || null,
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
throw new FortellisApiError(`Fortellis API call failed for ${apiName}: ${error.message}`, errorDetails);
|
||||
}
|
||||
}
|
||||
|
||||
//Some Fortellis calls return a batch result that isn't ready immediately.
|
||||
//This function will check the status of the call and wait until it is ready.
|
||||
//It will try 5 times before giving up.
|
||||
async function DelayedCallback({ delayMeta, access_token, SubscriptionID, ReqId, departmentIds }) {
|
||||
for (let index = 0; index < 5; index++) {
|
||||
await sleep(delayMeta.checkStatusAfterSeconds * 1000);
|
||||
//Check to see if the call is ready.
|
||||
const statusResult = await axios.get(delayMeta._links.status.href, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${access_token}`,
|
||||
"Subscription-Id": SubscriptionID,
|
||||
"Request-Id": ReqId,
|
||||
"Department-Id": departmentIds[0].id
|
||||
}
|
||||
});
|
||||
|
||||
//TODO: Add a check if the status result is not ready, to try again.
|
||||
if (statusResult.data.status === "complete") {
|
||||
//This may have to check again if it isn't ready.
|
||||
const batchResult = await axios.get(statusResult.data._links.result.href, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${access_token}`,
|
||||
"Subscription-Id": SubscriptionID,
|
||||
"Request-Id": ReqId
|
||||
//"Department-Id": departmentIds[0].id
|
||||
}
|
||||
});
|
||||
return batchResult;
|
||||
} else {
|
||||
return "Error!!! Still need to implement batch waiting.";
|
||||
}
|
||||
}
|
||||
}
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
const isProduction = process.env.NODE_ENV === "production";
|
||||
|
||||
//Get requests should have the trailing slash as they are used that way in the calls.
|
||||
const FortellisActions = {
|
||||
GetSubscription: {
|
||||
url: isProduction
|
||||
? "https://subscriptions.fortellis.io/v1/solution/subscriptions"
|
||||
: "https://subscriptions.fortellis.io/v1/solution/subscriptions",
|
||||
type: "get",
|
||||
apiName: "Fortellis Get Subscriptions"
|
||||
},
|
||||
QueryVehicles: {
|
||||
url: isProduction
|
||||
? "https://api.fortellis.io/cdkdrive/service/v1/vehicles/"
|
||||
: "https://api.fortellis.io/cdk-test/cdkdrive/service/v1/vehicles/",
|
||||
type: "get",
|
||||
apiName: "Service Vehicle - Query Vehicles"
|
||||
},
|
||||
GetMakeModel: {
|
||||
url: isProduction
|
||||
? "https://api.fortellis.io/cdk/drive/makemodel/v2/bulk"
|
||||
: "https://api.fortellis.io/cdk-test/drive/makemodel/v2",
|
||||
type: "get",
|
||||
apiName: "CDK Drive Get Make Model Lite"
|
||||
},
|
||||
GetVehicleId: {
|
||||
url: isProduction
|
||||
? "https://api.fortellis.io/cdk/drive/service-vehicle-mgmt/v2/vehicle-ids/" //Request path params of vins
|
||||
: "https://api.fortellis.io/cdk-test/drive/service-vehicle-mgmt/v2/vehicle-ids/",
|
||||
type: "get",
|
||||
apiName: "CDK Drive Post Service Vehicle"
|
||||
},
|
||||
GetVehicleById: {
|
||||
url: isProduction
|
||||
? "https://api.fortellis.io/cdk/drive/service-vehicle-mgmt/v2/" //Request path params of vehicleId
|
||||
: "https://api.fortellis.io/cdk-test/drive/service-vehicle-mgmt/v2/",
|
||||
type: "get",
|
||||
apiName: "CDK Drive Post Service Vehicle"
|
||||
},
|
||||
QueryCustomerByName: {
|
||||
url: isProduction
|
||||
? "https://api.fortellis.io/cdk/drive/customerpost/v1/search"
|
||||
: "https://api.fortellis.io/cdk-test/drive/customerpost/v1/search",
|
||||
type: "get",
|
||||
apiName: "CDK Drive Post Customer"
|
||||
},
|
||||
ReadCustomer: {
|
||||
url: isProduction
|
||||
? "https://api.fortellis.io/cdk/drive/customerpost/v1/" //Customer ID is request param.
|
||||
: "https://api.fortellis.io/cdk-test/drive/customerpost/v1/",
|
||||
type: "get",
|
||||
apiName: "CDK Drive Post Customer"
|
||||
},
|
||||
CreateCustomer: {
|
||||
url: isProduction
|
||||
? "https://api.fortellis.io/cdk/drive/customerpost/v1/"
|
||||
: "https://api.fortellis.io/cdk-test/drive/customerpost/v1/",
|
||||
type: "post",
|
||||
apiName: "CDK Drive Post Customer"
|
||||
},
|
||||
InsertVehicle: {
|
||||
url: isProduction
|
||||
? "https://api.fortellis.io/cdk/drive/service-vehicle-mgmt/v2/"
|
||||
: "https://api.fortellis.io/cdk-test/drive/service-vehicle-mgmt/v2/",
|
||||
type: "post",
|
||||
apiName: "CDK Drive Post Service Vehicle"
|
||||
},
|
||||
UpdateVehicle: {
|
||||
url: isProduction
|
||||
? "https://api.fortellis.io/cdk/drive/service-vehicle-mgmt/v2/"
|
||||
: "https://api.fortellis.io/cdk-test/drive/service-vehicle-mgmt/v2/",
|
||||
type: "put",
|
||||
apiName: "CDK Drive Post Service Vehicle"
|
||||
},
|
||||
GetCOA: {
|
||||
type: "get",
|
||||
apiName: "CDK Drive Post Accounts GL WIP",
|
||||
url: `https://api.fortellis.io/cdk-test/drive/chartofaccounts/v2/bulk/`,
|
||||
waitForResult: true
|
||||
},
|
||||
StartWip: {
|
||||
url: isProduction
|
||||
? "https://api.fortellis.io/cdk/drive/glpost/startWIP"
|
||||
: "https://api.fortellis.io/cdk-test/drive/glpost/startWIP",
|
||||
type: "post",
|
||||
apiName: "CDK Drive Post Accounts GL"
|
||||
},
|
||||
TranBatchWip: {
|
||||
url: isProduction
|
||||
? "https://api.fortellis.io/cdk/drive/glpost/transBatchWIP"
|
||||
: "https://api.fortellis.io/cdk-test/drive/glpost/transBatchWIP",
|
||||
type: "post",
|
||||
apiName: "CDK Drive Post Accounts GL"
|
||||
},
|
||||
PostBatchWip: {
|
||||
url: isProduction
|
||||
? "https://api.fortellis.io/cdk/drive/glpost/postBatchWIP"
|
||||
: "https://api.fortellis.io/cdk-test/drive/glpost/postBatchWIP",
|
||||
type: "post",
|
||||
apiName: "CDK Drive Post Accounts GL"
|
||||
},
|
||||
DeleteTranWip: {
|
||||
url: isProduction
|
||||
? "https://api.fortellis.io/cdk/drive/glpost/postWIP"
|
||||
: "https://api.fortellis.io/cdk-test/drive/glpost/postWIP",
|
||||
type: "post",
|
||||
apiName: "CDK Drive Post Accounts GL"
|
||||
},
|
||||
QueryErrorWip: {
|
||||
url: isProduction
|
||||
? "https://api.fortellis.io/cdk/drive/glpost/errWIP/"
|
||||
: "https://api.fortellis.io/cdk-test/drive/glpost/errWIP/",
|
||||
type: "get",
|
||||
apiName: "CDK Drive Post Accounts GL"
|
||||
},
|
||||
ServiceHistoryInsert: {
|
||||
url: isProduction
|
||||
? "https://api.fortellis.io/cdk/drive/post/service-vehicle-history-mgmt/v2/"
|
||||
: "https://api.fortellis.io/cdk-test/drive/post/service-vehicle-history-mgmt/v2/",
|
||||
type: "post",
|
||||
apiName: "CDK Drive Post Service Vehicle History"
|
||||
}
|
||||
};
|
||||
|
||||
const FortellisCacheEnums = {
|
||||
txEnvelope: "txEnvelope",
|
||||
DMSBatchTxn: "DMSBatchTxn",
|
||||
SubscriptionMeta: "SubscriptionMeta",
|
||||
DepartmentId: "DepartmentId",
|
||||
JobData: "JobData",
|
||||
DMSVid: "DMSVid",
|
||||
DMSVeh: "DMSVeh",
|
||||
DMSVehCustomer: "DMSVehCustomer",
|
||||
DMSCustList: "DMSCustList",
|
||||
DMSCust: "DMSCust",
|
||||
selectedCustomerId: "selectedCustomerId",
|
||||
DMSTransHeader: "DMSTransHeader",
|
||||
transWips: "transWips",
|
||||
DmsBatchTxnPost: "DmsBatchTxnPost",
|
||||
DMSVehHistory: "DMSVehHistory"
|
||||
};
|
||||
|
||||
function constructFullUrl({ url, pathParams = "", requestSearchParams = [] }) {
|
||||
// Ensure the base URL ends with a single "/"
|
||||
url = url.replace(/\/+$/, "/");
|
||||
const fullPath = pathParams ? `${url}${pathParams}` : url;
|
||||
const searchParams = new URLSearchParams(requestSearchParams).toString();
|
||||
const fullUrl = searchParams ? `${fullPath}?${searchParams}` : fullPath;
|
||||
return fullUrl;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
GetAuthToken,
|
||||
FortellisCacheEnums,
|
||||
MakeFortellisCall,
|
||||
FortellisActions,
|
||||
getTransactionType,
|
||||
defaultFortellisTTL,
|
||||
FortellisApiError,
|
||||
GetDepartmentId
|
||||
};
|
||||
8
server/fortellis/fortellis-logger.js
Normal file
8
server/fortellis/fortellis-logger.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const logger = require("../utils/logger");
|
||||
|
||||
const CreateFortellisLogEvent = (socket, level, message, txnDetails) => {
|
||||
logger.log("fortellis-log-event", level, socket?.user?.email, null, { wsmessage: message, txnDetails });
|
||||
socket.emit("fortellis-log-event", { level, message, txnDetails });
|
||||
};
|
||||
|
||||
module.exports = CreateFortellisLogEvent;
|
||||
1326
server/fortellis/fortellis.js
Normal file
1326
server/fortellis/fortellis.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1219,7 +1219,7 @@ query ENTEGRAL_EXPORT($bodyshopid: uuid!) {
|
||||
}`;
|
||||
|
||||
exports.KAIZEN_QUERY = `query KAIZEN_EXPORT($start: timestamptz, $bodyshopid: uuid!, $end: timestamptz) {
|
||||
bodyshops_by_pk(id: $bodyshopid) {
|
||||
bodyshops_by_pk(id: $bodyshopid){
|
||||
id
|
||||
shopname
|
||||
address1
|
||||
@@ -1246,24 +1246,15 @@ exports.KAIZEN_QUERY = `query KAIZEN_EXPORT($start: timestamptz, $bodyshopid: uu
|
||||
bills {
|
||||
billlines {
|
||||
actual_cost
|
||||
actual_price
|
||||
cost_center
|
||||
deductedfromlbr
|
||||
id
|
||||
line_desc
|
||||
quantity
|
||||
}
|
||||
date
|
||||
federal_tax_rate
|
||||
id
|
||||
is_credit_memo
|
||||
invoice_number
|
||||
isinhouse
|
||||
local_tax_rate
|
||||
state_tax_rate
|
||||
vendor {
|
||||
name
|
||||
}
|
||||
}
|
||||
created_at
|
||||
clm_no
|
||||
@@ -1305,7 +1296,7 @@ exports.KAIZEN_QUERY = `query KAIZEN_EXPORT($start: timestamptz, $bodyshopid: uu
|
||||
joblines(where: {removed: {_eq: false}}) {
|
||||
act_price
|
||||
alt_partno
|
||||
billlines(order_by: {bill: {date: desc_nulls_last}}, limit: 1) {
|
||||
billlines(order_by: {bill: {date: desc_nulls_last}} limit: 1) {
|
||||
actual_cost
|
||||
actual_price
|
||||
quantity
|
||||
@@ -1328,8 +1319,8 @@ exports.KAIZEN_QUERY = `query KAIZEN_EXPORT($start: timestamptz, $bodyshopid: uu
|
||||
mod_lbr_ty
|
||||
oem_partno
|
||||
op_code_desc
|
||||
parts_order_lines(order_by: {parts_order: {order_date: desc_nulls_last}}, limit: 1) {
|
||||
parts_order {
|
||||
parts_order_lines(order_by: {parts_order: {order_date: desc_nulls_last}} limit: 1){
|
||||
parts_order{
|
||||
id
|
||||
order_date
|
||||
}
|
||||
@@ -1348,14 +1339,6 @@ exports.KAIZEN_QUERY = `query KAIZEN_EXPORT($start: timestamptz, $bodyshopid: uu
|
||||
jobid
|
||||
totalliquidcost
|
||||
}
|
||||
notes {
|
||||
created_at
|
||||
created_by
|
||||
critical
|
||||
private
|
||||
text
|
||||
type
|
||||
}
|
||||
ownr_addr1
|
||||
ownr_addr2
|
||||
ownr_city
|
||||
@@ -2190,19 +2173,7 @@ mutation UPDATE_BILLS($billids: [uuid!]!, $bill: bills_set_input!, $logs: [expor
|
||||
}
|
||||
}`;
|
||||
|
||||
exports.INSERT_EXPORT_LOG = `
|
||||
mutation INSERT_EXPORT_LOG($log: exportlog_insert_input!) {
|
||||
insert_exportlog_one(object: $log) {
|
||||
id
|
||||
}
|
||||
}`;
|
||||
|
||||
exports.QUERY_EXISTING_TRANSITION = `
|
||||
mutation INSERT_EXPORT_LOG($log: exportlog_insert_input!) {
|
||||
insert_exportlog_one(object: $log) {
|
||||
id
|
||||
}
|
||||
}`;
|
||||
|
||||
exports.UPDATE_OLD_TRANSITION = `mutation UPDATE_OLD_TRANSITION($jobid: uuid!, $existingTransition: transitions_set_input!){
|
||||
update_transitions(where:{jobid:{_eq:$jobid}, end:{_is_null:true
|
||||
@@ -2221,18 +2192,16 @@ exports.UPDATE_OLD_TRANSITION = `mutation UPDATE_OLD_TRANSITION($jobid: uuid!, $
|
||||
|
||||
exports.INSERT_NEW_TRANSITION = (
|
||||
includeOldTransition
|
||||
) => `mutation INSERT_NEW_TRANSITION($newTransition: transitions_insert_input!, ${
|
||||
includeOldTransition ? `$oldTransitionId: uuid!, $duration: numeric` : ""
|
||||
}) {
|
||||
) => `mutation INSERT_NEW_TRANSITION($newTransition: transitions_insert_input!, ${includeOldTransition ? `$oldTransitionId: uuid!, $duration: numeric` : ""
|
||||
}) {
|
||||
insert_transitions_one(object: $newTransition) {
|
||||
id
|
||||
}
|
||||
${
|
||||
includeOldTransition
|
||||
? `update_transitions(where: {id: {_eq: $oldTransitionId}}, _set: {duration: $duration}) {
|
||||
${includeOldTransition
|
||||
? `update_transitions(where: {id: {_eq: $oldTransitionId}}, _set: {duration: $duration}) {
|
||||
affected_rows
|
||||
}`
|
||||
: ""
|
||||
: ""
|
||||
}
|
||||
}`;
|
||||
|
||||
@@ -2926,15 +2895,6 @@ exports.GET_BODYSHOP_BY_ID = `
|
||||
}
|
||||
`;
|
||||
|
||||
exports.GET_BODYSHOP_WATCHERS_BY_ID = `
|
||||
query GET_BODYSHOP_BY_ID($id: uuid!) {
|
||||
bodyshops_by_pk(id: $id) {
|
||||
id
|
||||
notification_followers
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
exports.GET_DOCUMENTS_BY_JOB = `
|
||||
query GET_DOCUMENTS_BY_JOB($jobId: uuid!) {
|
||||
jobs_by_pk(id: $jobId) {
|
||||
@@ -3180,10 +3140,11 @@ exports.DELETE_PHONE_NUMBER_OPT_OUT = `
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
exports.INSERT_MEDIA_ANALYTICS = `
|
||||
mutation INSERT_MEDIA_ANALYTICS($mediaObject: media_analytics_insert_input!) {
|
||||
insert_media_analytics_one(object: $mediaObject) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
`
|
||||
@@ -134,16 +134,13 @@ const insertUserAssociation = async (uid, email, shopId) => {
|
||||
|
||||
/**
|
||||
* PATCH handler for updating bodyshop fields.
|
||||
* Allows patching: shopname, address1, address2, city, state, zip_post, country, email, timezone, phone
|
||||
* Also allows updating logo_img_path via a simple logoUrl string, which is expanded to the full object.
|
||||
* Allows patching: shopname, address1, address2, city, state, zip_post, country, email, timezone, phone, logo_img_path
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const patchPartsManagementProvisioning = async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
// Fields that can be directly patched 1:1
|
||||
const allowedFields = [
|
||||
"shopname",
|
||||
"address1",
|
||||
@@ -154,58 +151,31 @@ const patchPartsManagementProvisioning = async (req, res) => {
|
||||
"country",
|
||||
"email",
|
||||
"timezone",
|
||||
"phone"
|
||||
// NOTE: logo_img_path is handled separately via logoUrl
|
||||
"phone",
|
||||
"logo_img_path"
|
||||
];
|
||||
|
||||
const updateFields = {};
|
||||
|
||||
// Copy over simple scalar fields if present
|
||||
for (const field of allowedFields) {
|
||||
if (req.body[field] !== undefined) {
|
||||
updateFields[field] = req.body[field];
|
||||
}
|
||||
}
|
||||
|
||||
// Handle logo update via a simple href string, same behavior as provision route
|
||||
if (typeof req.body.logo_img_path === "string") {
|
||||
const trimmed = req.body.logo_img_path.trim();
|
||||
if (trimmed) {
|
||||
updateFields.logo_img_path = {
|
||||
src: trimmed,
|
||||
width: "",
|
||||
height: "",
|
||||
headerMargin: DefaultNewShop.logo_img_path.headerMargin
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(updateFields).length === 0) {
|
||||
return res.status(400).json({ error: "No valid fields provided for update." });
|
||||
}
|
||||
|
||||
// Check that the bodyshop has an external_shop_id before allowing patch
|
||||
try {
|
||||
// Fetch the bodyshop by id
|
||||
const shopResp = await client.request(
|
||||
`query GetBodyshop($id: uuid!) {
|
||||
bodyshops_by_pk(id: $id) {
|
||||
id
|
||||
external_shop_id
|
||||
}
|
||||
}`,
|
||||
`query GetBodyshop($id: uuid!) { bodyshops_by_pk(id: $id) { id external_shop_id } }`,
|
||||
{ id }
|
||||
);
|
||||
|
||||
if (!shopResp.bodyshops_by_pk?.external_shop_id) {
|
||||
return res.status(400).json({ error: "Cannot patch: bodyshop does not have an external_shop_id." });
|
||||
}
|
||||
} catch (err) {
|
||||
return res.status(500).json({
|
||||
error: "Failed to validate bodyshop external_shop_id.",
|
||||
detail: err
|
||||
});
|
||||
return res.status(500).json({ error: "Failed to validate bodyshop external_shop_id.", detail: err });
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await client.request(UPDATE_BODYSHOP_BY_ID, { id, fields: updateFields });
|
||||
if (!resp.update_bodyshops_by_pk) {
|
||||
@@ -241,8 +211,6 @@ const partsManagementProvisioning = async (req, res) => {
|
||||
"phone",
|
||||
"userEmail"
|
||||
]);
|
||||
|
||||
// TODO add in check for early access
|
||||
await ensureExternalIdUnique(body.external_shop_id);
|
||||
|
||||
logger.log("admin-create-shop-user", "debug", body.userEmail, null, {
|
||||
|
||||
@@ -4,14 +4,11 @@
|
||||
* This module handles automatically adding watchers to new jobs based on the notifications_autoadd
|
||||
* boolean field in the associations table and the notification_followers JSON field in the bodyshops table.
|
||||
* It ensures users are not added twice and logs the process.
|
||||
*
|
||||
* NOTE: Bodyshop notification_followers is fetched directly from the DB (Hasura) to avoid stale Redis cache.
|
||||
*/
|
||||
|
||||
const { client: gqlClient } = require("../graphql-client/graphql-client");
|
||||
const { isEmpty } = require("lodash");
|
||||
const {
|
||||
GET_BODYSHOP_WATCHERS_BY_ID,
|
||||
GET_JOB_WATCHERS_MINIMAL,
|
||||
GET_NOTIFICATION_WATCHERS,
|
||||
INSERT_JOB_WATCHERS
|
||||
@@ -29,7 +26,10 @@ const FILTER_SELF_FROM_WATCHERS = process.env?.FILTER_SELF_FROM_WATCHERS !== "fa
|
||||
*/
|
||||
const autoAddWatchers = async (req) => {
|
||||
const { event, trigger } = req.body;
|
||||
const { logger } = req;
|
||||
const {
|
||||
logger,
|
||||
sessionUtils: { getBodyshopFromRedis }
|
||||
} = req;
|
||||
|
||||
// Validate that this is an INSERT event, bail
|
||||
if (trigger?.name !== "notifications_jobs_autoadd" || event.op !== "INSERT" || event.data.old) {
|
||||
@@ -48,20 +48,20 @@ const autoAddWatchers = async (req) => {
|
||||
const hasuraUserId = event?.session_variables?.["x-hasura-user-id"];
|
||||
|
||||
try {
|
||||
// Fetch bodyshop data directly from DB (avoid Redis staleness)
|
||||
const bodyshopResponse = await gqlClient.request(GET_BODYSHOP_WATCHERS_BY_ID, { id: shopId });
|
||||
const bodyshopData = bodyshopResponse?.bodyshops_by_pk;
|
||||
// Fetch bodyshop data from Redis
|
||||
const bodyshopData = await getBodyshopFromRedis(shopId);
|
||||
let notificationFollowers = bodyshopData?.notification_followers;
|
||||
|
||||
const notificationFollowersRaw = bodyshopData?.notification_followers;
|
||||
const notificationFollowers = Array.isArray(notificationFollowersRaw)
|
||||
? [...new Set(notificationFollowersRaw.filter((id) => id))] // de-dupe + remove falsy
|
||||
: [];
|
||||
// Bail if notification_followers is missing or not an array
|
||||
if (!notificationFollowers || !Array.isArray(notificationFollowers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute queries in parallel
|
||||
const [notificationData, existingWatchersData] = await Promise.all([
|
||||
gqlClient.request(GET_NOTIFICATION_WATCHERS, {
|
||||
shopId,
|
||||
employeeIds: notificationFollowers
|
||||
employeeIds: notificationFollowers.filter((id) => id)
|
||||
}),
|
||||
gqlClient.request(GET_JOB_WATCHERS_MINIMAL, { jobid: jobId })
|
||||
]);
|
||||
@@ -73,7 +73,7 @@ const autoAddWatchers = async (req) => {
|
||||
associationId: assoc.id
|
||||
})) || [];
|
||||
|
||||
// Get users from notification_followers (employee IDs -> employee emails)
|
||||
// Get users from notification_followers
|
||||
const followerEmails =
|
||||
notificationData?.employees
|
||||
?.filter((e) => e.user_email)
|
||||
@@ -84,7 +84,7 @@ const autoAddWatchers = async (req) => {
|
||||
|
||||
// Combine and deduplicate emails (use email as the unique key)
|
||||
const usersToAdd = [...autoAddUsers, ...followerEmails].reduce((acc, user) => {
|
||||
if (user?.email && !acc.some((u) => u.email === user.email)) {
|
||||
if (!acc.some((u) => u.email === user.email)) {
|
||||
acc.push(user);
|
||||
}
|
||||
return acc;
|
||||
@@ -123,7 +123,6 @@ const autoAddWatchers = async (req) => {
|
||||
message: error?.message,
|
||||
stack: error?.stack,
|
||||
jobId,
|
||||
shopId,
|
||||
roNumber
|
||||
});
|
||||
throw error; // Re-throw to ensure the error is logged in the handler
|
||||
|
||||
@@ -81,8 +81,8 @@ const alternateTransportChangedBuilder = (data) => {
|
||||
* @returns {{app: {jobId, jobRoNumber: *, bodyShopId: *, key: string, body: string, variables: Object, recipients: *[]}, email: {jobId, jobRoNumber: *, bodyShopName: *, body: string, recipients: *[]}, fcm: {recipients: *[]}}}
|
||||
*/
|
||||
const billPostedBuilder = (data) => {
|
||||
const facing = data?.data?.isinhouse ? "An In House" : "A Vendor";
|
||||
const body = `${facing} ${data?.data?.is_credit_memo ? "credit memo" : "bill"} has been posted.`.trim();
|
||||
const facing = data?.data?.isinhouse ? "in-house" : "vendor";
|
||||
const body = `An ${facing} ${data?.data?.is_credit_memo ? "credit memo" : "bill"} has been posted.`.trim();
|
||||
|
||||
return buildNotification(data, "notifications.job.billPosted", body, {
|
||||
isInHouse: data?.data?.isinhouse,
|
||||
|
||||
@@ -8,6 +8,7 @@ const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLCl
|
||||
router.use(validateFirebaseIdTokenMiddleware);
|
||||
|
||||
router.post("/getvehicles", withUserGraphQLClientMiddleware, cdkGetMake.default);
|
||||
router.post("/fortellis/getvehicles", withUserGraphQLClientMiddleware, cdkGetMake.fortellis);
|
||||
router.post("/calculate-allocations", withUserGraphQLClientMiddleware, cdkCalculateAllocations.defaultRoute);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -19,6 +19,7 @@ const getBodyshopCacheKey = (bodyshopId) => `bodyshop-cache:${bodyshopId}`;
|
||||
const getUserSocketMappingKey = (email) =>
|
||||
`user:${process.env?.NODE_ENV === "production" ? "prod" : "dev"}:${email}:socketMapping`;
|
||||
|
||||
const getSocketTransactionkey = ({ socketId, transactionType }) => `socket:${socketId}:${transactionType}`;
|
||||
/**
|
||||
* Fetch bodyshop data from the database
|
||||
* @param bodyshopId
|
||||
@@ -51,9 +52,12 @@ const fetchBodyshopFromDB = async (bodyshopId, logger) => {
|
||||
*/
|
||||
const applyRedisHelpers = ({ pubClient, app, logger }) => {
|
||||
// Store session data in Redis
|
||||
const setSessionData = async (socketId, key, value) => {
|
||||
const setSessionData = async (socketId, key, value, ttl) => {
|
||||
try {
|
||||
await pubClient.hset(`socket:${socketId}`, key, JSON.stringify(value)); // Use Redis pubClient
|
||||
await pubClient.hset(`socket:${socketId}`, key, JSON.stringify(value), ttl); // Use Redis pubClient
|
||||
if (ttl && typeof ttl === "number") {
|
||||
await pubClient.expire(`socket:${socketId}`, ttl);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.log(`Error Setting Session Data for socket ${socketId}: ${error}`, "ERROR", "redis");
|
||||
}
|
||||
@@ -69,6 +73,35 @@ const applyRedisHelpers = ({ pubClient, app, logger }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const setSessionTransactionData = async (socketId, transactionType, key, value, ttl) => {
|
||||
try {
|
||||
await pubClient.hset(getSocketTransactionkey({ socketId, transactionType }), key, JSON.stringify(value)); // Use Redis pubClient
|
||||
if (ttl && typeof ttl === "number") {
|
||||
await pubClient.expire(getSocketTransactionkey({ socketId, transactionType }), ttl);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.log(
|
||||
`Error Setting Session Data for socket transaction ${socketId}:${transactionType}: ${error}`,
|
||||
"ERROR",
|
||||
"redis"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Retrieve session transaction data from Redis
|
||||
const getSessionTransactionData = async (socketId, transactionType, key) => {
|
||||
try {
|
||||
const data = await pubClient.hget(getSocketTransactionkey({ socketId, transactionType }), key);
|
||||
return data ? JSON.parse(data) : null;
|
||||
} catch (error) {
|
||||
logger.log(
|
||||
`Error Getting Session Data for socket transaction ${socketId}:${transactionType}: ${error}`,
|
||||
"ERROR",
|
||||
"redis"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Clear session data from Redis
|
||||
const clearSessionData = async (socketId) => {
|
||||
try {
|
||||
@@ -77,6 +110,18 @@ const applyRedisHelpers = ({ pubClient, app, logger }) => {
|
||||
logger.log(`Error Clearing Session Data for socket ${socketId}: ${error}`, "ERROR", "redis");
|
||||
}
|
||||
};
|
||||
// Clear session data from Redis
|
||||
const clearSessionTransactionData = async (socketId, transactionType) => {
|
||||
try {
|
||||
await pubClient.del(getSocketTransactionkey({ socketId, transactionType }));
|
||||
} catch (error) {
|
||||
logger.log(
|
||||
`Error Clearing Session Transaction Data for socket ${socketId}:${transactionType}: ${error}`,
|
||||
"ERROR",
|
||||
"redis"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a socket mapping for a user
|
||||
@@ -394,7 +439,10 @@ const applyRedisHelpers = ({ pubClient, app, logger }) => {
|
||||
getUserSocketMapping,
|
||||
refreshUserSocketTTL,
|
||||
getBodyshopFromRedis,
|
||||
updateOrInvalidateBodyshopFromRedis
|
||||
updateOrInvalidateBodyshopFromRedis,
|
||||
setSessionTransactionData,
|
||||
getSessionTransactionData,
|
||||
clearSessionTransactionData
|
||||
// setMultipleSessionData,
|
||||
// getMultipleSessionData,
|
||||
// setMultipleFromArraySessionData,
|
||||
|
||||
116
server/web-sockets/createLogEvent.js
Normal file
116
server/web-sockets/createLogEvent.js
Normal file
@@ -0,0 +1,116 @@
|
||||
const { isArray } = require("lodash");
|
||||
const logger = require("../utils/logger");
|
||||
|
||||
|
||||
function createLogEvent(socket, level, message) {
|
||||
if (LogLevelHierarchy(socket.log_level) >= LogLevelHierarchy(level)) {
|
||||
// console.log(`[WS LOG EVENT] ${level} - ${new Date()} - ${socket.user.email} - ${socket.id} - ${message}`);
|
||||
socket.emit("log-event", {
|
||||
timestamp: new Date(),
|
||||
level,
|
||||
message
|
||||
});
|
||||
|
||||
logger.log("ws-log-event", level, socket.user.email, socket.recordid, {
|
||||
wsmessage: message
|
||||
});
|
||||
|
||||
if (socket.logEvents && isArray(socket.logEvents)) {
|
||||
socket.logEvents.push({
|
||||
timestamp: new Date(),
|
||||
level,
|
||||
message
|
||||
});
|
||||
}
|
||||
// if (level === "ERROR") {
|
||||
// throw new Error(message);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
function createJsonEvent(socket, level, message, json) {
|
||||
if (LogLevelHierarchy(socket.log_level) >= LogLevelHierarchy(level)) {
|
||||
//console.log(`[WS LOG EVENT] ${level} - ${new Date()} - ${socket.user.email} - ${socket.id} - ${message}`);
|
||||
socket.emit("log-event", {
|
||||
timestamp: new Date(),
|
||||
level,
|
||||
message
|
||||
});
|
||||
}
|
||||
logger.log(
|
||||
"ws-log-event-json",
|
||||
level,
|
||||
socket.user.email,
|
||||
socket.recordid,
|
||||
{
|
||||
wsmessage: message,
|
||||
json
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
if (socket.logEvents && isArray(socket.logEvents)) {
|
||||
socket.logEvents.push({
|
||||
timestamp: new Date(),
|
||||
level,
|
||||
message
|
||||
});
|
||||
}
|
||||
// if (level === "ERROR") {
|
||||
// throw new Error(message);
|
||||
// }
|
||||
}
|
||||
|
||||
function createXmlEvent(socket, xml, message, isError = false) {
|
||||
if (LogLevelHierarchy(socket.log_level) >= LogLevelHierarchy("SILLY")) {
|
||||
socket.emit("log-event", {
|
||||
timestamp: new Date(),
|
||||
level: isError ? "ERROR" : "SILLY",
|
||||
message: `${message}: ${xml}`
|
||||
});
|
||||
}
|
||||
|
||||
logger.log(
|
||||
isError ? "ws-log-event-xml-error" : "ws-log-event-xml",
|
||||
isError ? "ERROR" : "SILLY",
|
||||
socket.user.email,
|
||||
socket.recordid,
|
||||
{
|
||||
wsmessage: message,
|
||||
xml
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
if (socket.logEvents && isArray(socket.logEvents)) {
|
||||
socket.logEvents.push({
|
||||
timestamp: new Date(),
|
||||
level: isError ? "ERROR" : "SILLY",
|
||||
message,
|
||||
xml
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function LogLevelHierarchy(level) {
|
||||
switch (level) {
|
||||
case "XML":
|
||||
return 5;
|
||||
case "SILLY":
|
||||
return 5;
|
||||
case "DEBUG":
|
||||
return 4;
|
||||
case "INFO":
|
||||
return 3;
|
||||
case "WARN":
|
||||
return 2;
|
||||
case "ERROR":
|
||||
return 1;
|
||||
default:
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
exports.createLogEvent = createLogEvent;
|
||||
exports.createXmlEvent = createXmlEvent;
|
||||
exports.createJsonEvent = createJsonEvent;
|
||||
@@ -1,8 +1,20 @@
|
||||
const { admin } = require("../firebase/firebase-handler");
|
||||
|
||||
const { FortellisJobExport, FortellisSelectedCustomer } = require("../fortellis/fortellis");
|
||||
const FortellisLogger = require("../fortellis/fortellis-logger");
|
||||
const CdkCalculateAllocations = require("../cdk/cdk-calculate-allocations").default;
|
||||
const redisSocketEvents = ({
|
||||
io,
|
||||
redisHelpers: { addUserSocketMapping, removeUserSocketMapping, refreshUserSocketTTL, getUserSocketMappingByBodyshop },
|
||||
redisHelpers: {
|
||||
setSessionData,
|
||||
getSessionData,
|
||||
addUserSocketMapping,
|
||||
removeUserSocketMapping,
|
||||
refreshUserSocketTTL,
|
||||
getUserSocketMappingByBodyshop,
|
||||
setSessionTransactionData,
|
||||
getSessionTransactionData,
|
||||
clearSessionTransactionData
|
||||
},
|
||||
ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom },
|
||||
logger
|
||||
}) => {
|
||||
@@ -27,6 +39,8 @@ const redisSocketEvents = ({
|
||||
const user = await admin.auth().verifyIdToken(token);
|
||||
socket.user = user;
|
||||
socket.bodyshopId = bodyshopId;
|
||||
socket.data = socket.data || {};
|
||||
socket.data.authToken = token;
|
||||
await addUserSocketMapping(user.email, socket.id, bodyshopId);
|
||||
next();
|
||||
} catch (error) {
|
||||
@@ -50,13 +64,21 @@ const redisSocketEvents = ({
|
||||
}
|
||||
|
||||
try {
|
||||
const user = await admin.auth().verifyIdToken(token, true);
|
||||
const user = await admin.auth().verifyIdToken(token);
|
||||
if (currentTimestamp < latestTokenTimestamp) {
|
||||
createLogEvent(socket, "warn", "Outdated token validation skipped.");
|
||||
return;
|
||||
}
|
||||
socket.user = user;
|
||||
socket.bodyshopId = bodyshopId;
|
||||
// 🔑 keep the live token in a mutable place used by downstream code
|
||||
socket.data = socket.data || {};
|
||||
socket.data.authToken = token;
|
||||
// also keep handshake in sync for any legacy reads
|
||||
if (socket.handshake?.auth) {
|
||||
socket.handshake.auth.token = token;
|
||||
socket.handshake.auth.bodyshopId = bodyshopId;
|
||||
}
|
||||
await refreshUserSocketTTL(user.email, bodyshopId);
|
||||
socket.emit("token-updated", { success: true });
|
||||
} catch (error) {
|
||||
@@ -217,6 +239,76 @@ const redisSocketEvents = ({
|
||||
});
|
||||
};
|
||||
|
||||
//Fortellis/CDK Handlers
|
||||
const registerFortellisEvents = (socket) => {
|
||||
socket.on("fortellis-export-job", async ({ jobid, txEnvelope }) => {
|
||||
try {
|
||||
await FortellisJobExport({
|
||||
socket,
|
||||
redisHelpers: {
|
||||
setSessionData,
|
||||
getSessionData,
|
||||
addUserSocketMapping,
|
||||
removeUserSocketMapping,
|
||||
refreshUserSocketTTL,
|
||||
getUserSocketMappingByBodyshop,
|
||||
setSessionTransactionData,
|
||||
getSessionTransactionData,
|
||||
clearSessionTransactionData
|
||||
},
|
||||
ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom },
|
||||
jobid,
|
||||
txEnvelope
|
||||
});
|
||||
} catch (error) {
|
||||
FortellisLogger(socket, "error", `Error during Fortellis export : ${error.message}`);
|
||||
logger.log("fortellis-job-export-error", "error", socket.user?.email, jobid, {
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
}
|
||||
});
|
||||
socket.on("fortellis-selected-customer", async ({ jobid, selectedCustomerId }) => {
|
||||
try {
|
||||
await FortellisSelectedCustomer({
|
||||
socket,
|
||||
redisHelpers: {
|
||||
setSessionData,
|
||||
getSessionData,
|
||||
addUserSocketMapping,
|
||||
removeUserSocketMapping,
|
||||
refreshUserSocketTTL,
|
||||
getUserSocketMappingByBodyshop,
|
||||
setSessionTransactionData,
|
||||
getSessionTransactionData,
|
||||
clearSessionTransactionData
|
||||
},
|
||||
ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom },
|
||||
jobid,
|
||||
selectedCustomerId
|
||||
});
|
||||
} catch (error) {
|
||||
FortellisLogger(socket, "error", `Error during Fortellis export : ${error.message}`);
|
||||
logger.log("fortellis-selectd-customer-error", "error", socket.user?.email, jobid, {
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
}
|
||||
});
|
||||
socket.on("fortellis-calculate-allocations", async (jobid, callback) => {
|
||||
try {
|
||||
const allocations = await CdkCalculateAllocations(socket, jobid);
|
||||
callback(allocations);
|
||||
} catch (error) {
|
||||
FortellisLogger(socket, "error", `Error during Fortellis export : ${error.message}`);
|
||||
logger.log("fortellis-selectd-customer-error", "error", socket.user?.email, jobid, {
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Task Events
|
||||
const registerTaskEvents = (socket) => {
|
||||
socket.on("task-created", (payload) => {
|
||||
@@ -245,6 +337,7 @@ const redisSocketEvents = ({
|
||||
registerDisconnectEvents(socket);
|
||||
registerSyncEvents(socket);
|
||||
registerTaskEvents(socket);
|
||||
registerFortellisEvents(socket);
|
||||
};
|
||||
|
||||
// Associate Middleware and Handlers
|
||||
|
||||
@@ -1,231 +1,121 @@
|
||||
const path = require("path");
|
||||
|
||||
const { io } = require("../../server");
|
||||
const { admin } = require("../firebase/firebase-handler");
|
||||
const { default: CdkJobExport, CdkSelectedCustomer } = require("../cdk/cdk-job-export");
|
||||
const CdkGetMakes = require("../cdk/cdk-get-makes").default;
|
||||
const CdkCalculateAllocations = require("../cdk/cdk-calculate-allocations").default;
|
||||
const { isArray } = require("lodash");
|
||||
const logger = require("../utils/logger");
|
||||
const { default: PbsExportJob, PbsSelectedCustomer } = require("../accounting/pbs/pbs-job-export");
|
||||
|
||||
const { PbsCalculateAllocationsAp, PbsExportAp } = require("../accounting/pbs/pbs-ap-allocations");
|
||||
const { createLogEvent } = require("./createLogEvent");
|
||||
|
||||
io.use(function (socket, next) {
|
||||
try {
|
||||
if (socket.handshake.auth.token) {
|
||||
admin
|
||||
.auth()
|
||||
.verifyIdToken(socket.handshake.auth.token)
|
||||
.then((user) => {
|
||||
socket.user = user;
|
||||
next();
|
||||
})
|
||||
.catch((error) => {
|
||||
next(new Error("Authentication error", JSON.stringify(error)));
|
||||
});
|
||||
} else {
|
||||
next(new Error("Authentication error - no authorization token."));
|
||||
}
|
||||
} catch (error) {
|
||||
//console.log("Uncaught connection error:::", error);
|
||||
logger.log("websocket-connection-error", "error", null, null, {
|
||||
token: socket.handshake.auth.token,
|
||||
...error
|
||||
});
|
||||
next(new Error(`Authentication error ${error}`));
|
||||
}
|
||||
});
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
socket.log_level = "DEBUG";
|
||||
createLogEvent(socket, "DEBUG", `Connected and Authenticated.`);
|
||||
|
||||
socket.on("set-log-level", (level) => {
|
||||
socket.log_level = level;
|
||||
socket.emit("log-event", {
|
||||
timestamp: new Date(),
|
||||
level: "INFO",
|
||||
message: `Updated log level to ${level}`
|
||||
});
|
||||
});
|
||||
|
||||
///CDK
|
||||
socket.on("cdk-export-job", (jobid) => {
|
||||
CdkJobExport(socket, jobid);
|
||||
});
|
||||
socket.on("cdk-selected-customer", (selectedCustomerId) => {
|
||||
createLogEvent(socket, "DEBUG", `User selected customer ID ${selectedCustomerId}`);
|
||||
socket.selectedCustomerId = selectedCustomerId;
|
||||
CdkSelectedCustomer(socket, selectedCustomerId);
|
||||
});
|
||||
|
||||
socket.on("cdk-get-makes", async (cdk_dealerid, callback) => {
|
||||
function SetLegacyWebsocketHandlers(io) {
|
||||
io.use(function (socket, next) {
|
||||
try {
|
||||
const makes = await CdkGetMakes(socket, cdk_dealerid);
|
||||
callback(makes);
|
||||
if (socket.handshake.auth.token) {
|
||||
admin
|
||||
.auth()
|
||||
.verifyIdToken(socket.handshake.auth.token)
|
||||
.then((user) => {
|
||||
socket.user = user;
|
||||
next();
|
||||
})
|
||||
.catch((error) => {
|
||||
next(new Error("Authentication error", JSON.stringify(error)));
|
||||
});
|
||||
} else {
|
||||
next(new Error("Authentication error - no authorization token."));
|
||||
}
|
||||
} catch (error) {
|
||||
createLogEvent(socket, "ERROR", `Error in cdk-get-makes WS call. ${JSON.stringify(error, null, 2)}`);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("cdk-calculate-allocations", async (jobid, callback) => {
|
||||
const allocations = await CdkCalculateAllocations(socket, jobid);
|
||||
createLogEvent(socket, "DEBUG", `Allocations calculated.`);
|
||||
createLogEvent(socket, "SILLY", `Allocations calculated. ${JSON.stringify(allocations, null, 2)}`);
|
||||
|
||||
callback(allocations);
|
||||
});
|
||||
//END CDK
|
||||
|
||||
//PBS AR
|
||||
socket.on("pbs-calculate-allocations", async (jobid, callback) => {
|
||||
const allocations = await CdkCalculateAllocations(socket, jobid);
|
||||
createLogEvent(socket, "DEBUG", `Allocations calculated.`);
|
||||
createLogEvent(socket, "SILLY", `Allocations calculated. ${JSON.stringify(allocations, null, 2)}`);
|
||||
|
||||
callback(allocations);
|
||||
});
|
||||
socket.on("pbs-export-job", (jobid) => {
|
||||
PbsExportJob(socket, jobid);
|
||||
});
|
||||
socket.on("pbs-selected-customer", (selectedCustomerId) => {
|
||||
createLogEvent(socket, "DEBUG", `User selected customer ID ${selectedCustomerId}`);
|
||||
socket.selectedCustomerId = selectedCustomerId;
|
||||
PbsSelectedCustomer(socket, selectedCustomerId);
|
||||
});
|
||||
//End PBS AR
|
||||
|
||||
//PBS AP
|
||||
socket.on("pbs-calculate-allocations-ap", async (billids, callback) => {
|
||||
const allocations = await PbsCalculateAllocationsAp(socket, billids);
|
||||
createLogEvent(socket, "DEBUG", `AP Allocations calculated.`);
|
||||
createLogEvent(socket, "DEBUG", `Allocations calculated. ${JSON.stringify(allocations, null, 2)}`);
|
||||
socket.apAllocations = allocations;
|
||||
callback(allocations);
|
||||
});
|
||||
|
||||
socket.on("pbs-export-ap", ({ billids, txEnvelope }) => {
|
||||
socket.txEnvelope = txEnvelope;
|
||||
PbsExportAp(socket, { billids, txEnvelope });
|
||||
});
|
||||
|
||||
//END PBS AP
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
createLogEvent(socket, "DEBUG", `User disconnected.`);
|
||||
});
|
||||
});
|
||||
|
||||
function createLogEvent(socket, level, message) {
|
||||
if (LogLevelHierarchy(socket.log_level) >= LogLevelHierarchy(level)) {
|
||||
// console.log(`[WS LOG EVENT] ${level} - ${new Date()} - ${socket.user.email} - ${socket.id} - ${message}`);
|
||||
socket.emit("log-event", {
|
||||
timestamp: new Date(),
|
||||
level,
|
||||
message
|
||||
});
|
||||
|
||||
logger.log("ws-log-event", level, socket.user.email, socket.recordid, {
|
||||
wsmessage: message
|
||||
});
|
||||
|
||||
if (socket.logEvents && isArray(socket.logEvents)) {
|
||||
socket.logEvents.push({
|
||||
timestamp: new Date(),
|
||||
level,
|
||||
message
|
||||
//console.log("Uncaught connection error:::", error);
|
||||
logger.log("websocket-connection-error", "error", null, null, {
|
||||
token: socket.handshake.auth.token,
|
||||
...error
|
||||
});
|
||||
next(new Error(`Authentication error ${error}`));
|
||||
}
|
||||
// if (level === "ERROR") {
|
||||
// throw new Error(message);
|
||||
// }
|
||||
}
|
||||
});
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
socket.log_level = "DEBUG";
|
||||
createLogEvent(socket, "DEBUG", `Connected and Authenticated.`);
|
||||
|
||||
socket.on("set-log-level", (level) => {
|
||||
socket.log_level = level;
|
||||
socket.emit("log-event", {
|
||||
timestamp: new Date(),
|
||||
level: "INFO",
|
||||
message: `Updated log level to ${level}`
|
||||
});
|
||||
});
|
||||
|
||||
///CDK
|
||||
socket.on("cdk-export-job", (jobid) => {
|
||||
CdkJobExport(socket, jobid);
|
||||
});
|
||||
|
||||
socket.on("cdk-selected-customer", (selectedCustomerId) => {
|
||||
createLogEvent(socket, "DEBUG", `User selected customer ID ${selectedCustomerId}`);
|
||||
socket.selectedCustomerId = selectedCustomerId;
|
||||
CdkSelectedCustomer(socket, selectedCustomerId);
|
||||
});
|
||||
|
||||
socket.on("cdk-get-makes", async (cdk_dealerid, callback) => {
|
||||
try {
|
||||
const makes = await CdkGetMakes(socket, cdk_dealerid);
|
||||
callback(makes);
|
||||
} catch (error) {
|
||||
createLogEvent(socket, "ERROR", `Error in cdk-get-makes WS call. ${JSON.stringify(error, null, 2)}`);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("cdk-calculate-allocations", async (jobid, callback) => {
|
||||
console.log("cdk-calculate-allocations called", typeof CdkCalculateAllocations);
|
||||
const allocations = await CdkCalculateAllocations(socket, jobid);
|
||||
createLogEvent(socket, "DEBUG", `Allocations calculated.`);
|
||||
createLogEvent(socket, "SILLY", `Allocations calculated. ${JSON.stringify(allocations, null, 2)}`);
|
||||
|
||||
callback(allocations);
|
||||
});
|
||||
//END CDK
|
||||
|
||||
//PBS AR
|
||||
socket.on("pbs-calculate-allocations", async (jobid, callback) => {
|
||||
const allocations = await CdkCalculateAllocations(socket, jobid);
|
||||
createLogEvent(socket, "DEBUG", `Allocations calculated.`);
|
||||
createLogEvent(socket, "SILLY", `Allocations calculated. ${JSON.stringify(allocations, null, 2)}`);
|
||||
|
||||
callback(allocations);
|
||||
});
|
||||
socket.on("pbs-export-job", (jobid) => {
|
||||
PbsExportJob(socket, jobid);
|
||||
});
|
||||
socket.on("pbs-selected-customer", (selectedCustomerId) => {
|
||||
createLogEvent(socket, "DEBUG", `User selected customer ID ${selectedCustomerId}`);
|
||||
socket.selectedCustomerId = selectedCustomerId;
|
||||
PbsSelectedCustomer(socket, selectedCustomerId);
|
||||
});
|
||||
//End PBS AR
|
||||
|
||||
//PBS AP
|
||||
socket.on("pbs-calculate-allocations-ap", async (billids, callback) => {
|
||||
const allocations = await PbsCalculateAllocationsAp(socket, billids);
|
||||
createLogEvent(socket, "DEBUG", `AP Allocations calculated.`);
|
||||
createLogEvent(socket, "DEBUG", `Allocations calculated. ${JSON.stringify(allocations, null, 2)}`);
|
||||
socket.apAllocations = allocations;
|
||||
callback(allocations);
|
||||
});
|
||||
|
||||
socket.on("pbs-export-ap", ({ billids, txEnvelope }) => {
|
||||
socket.txEnvelope = txEnvelope;
|
||||
PbsExportAp(socket, { billids, txEnvelope });
|
||||
});
|
||||
|
||||
//END PBS AP
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
createLogEvent(socket, "DEBUG", `User disconnected.`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createJsonEvent(socket, level, message, json) {
|
||||
if (LogLevelHierarchy(socket.log_level) >= LogLevelHierarchy(level)) {
|
||||
//console.log(`[WS LOG EVENT] ${level} - ${new Date()} - ${socket.user.email} - ${socket.id} - ${message}`);
|
||||
socket.emit("log-event", {
|
||||
timestamp: new Date(),
|
||||
level,
|
||||
message
|
||||
});
|
||||
}
|
||||
logger.log(
|
||||
"ws-log-event-json",
|
||||
level,
|
||||
socket.user.email,
|
||||
socket.recordid,
|
||||
{
|
||||
wsmessage: message,
|
||||
json
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
if (socket.logEvents && isArray(socket.logEvents)) {
|
||||
socket.logEvents.push({
|
||||
timestamp: new Date(),
|
||||
level,
|
||||
message
|
||||
});
|
||||
}
|
||||
// if (level === "ERROR") {
|
||||
// throw new Error(message);
|
||||
// }
|
||||
}
|
||||
|
||||
function createXmlEvent(socket, xml, message, isError = false) {
|
||||
if (LogLevelHierarchy(socket.log_level) >= LogLevelHierarchy("SILLY")) {
|
||||
socket.emit("log-event", {
|
||||
timestamp: new Date(),
|
||||
level: isError ? "ERROR" : "SILLY",
|
||||
message: `${message}: ${xml}`
|
||||
});
|
||||
}
|
||||
|
||||
logger.log(
|
||||
isError ? "ws-log-event-xml-error" : "ws-log-event-xml",
|
||||
isError ? "ERROR" : "SILLY",
|
||||
socket.user.email,
|
||||
socket.recordid,
|
||||
{
|
||||
wsmessage: message,
|
||||
xml
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
if (socket.logEvents && isArray(socket.logEvents)) {
|
||||
socket.logEvents.push({
|
||||
timestamp: new Date(),
|
||||
level: isError ? "ERROR" : "SILLY",
|
||||
message,
|
||||
xml
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function LogLevelHierarchy(level) {
|
||||
switch (level) {
|
||||
case "XML":
|
||||
return 5;
|
||||
case "SILLY":
|
||||
return 5;
|
||||
case "DEBUG":
|
||||
return 4;
|
||||
case "INFO":
|
||||
return 3;
|
||||
case "WARN":
|
||||
return 2;
|
||||
case "ERROR":
|
||||
return 1;
|
||||
default:
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
exports.createLogEvent = createLogEvent;
|
||||
exports.createXmlEvent = createXmlEvent;
|
||||
exports.createJsonEvent = createJsonEvent;
|
||||
exports.SetLegacyWebsocketHandlers = SetLegacyWebsocketHandlers;
|
||||
|
||||
Reference in New Issue
Block a user