Compare commits
57 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63ce7b5c79 | ||
|
|
79a3b58a86 | ||
|
|
45dd3d8cd6 | ||
|
|
4a7bb07345 | ||
|
|
01fec9fa79 | ||
|
|
2f88d613c3 | ||
|
|
c9467b3982 | ||
|
|
ca1a456312 | ||
|
|
ca4c48bd5c | ||
|
|
e5fd5c8bcb | ||
|
|
46945a24a7 | ||
|
|
be746500a6 | ||
|
|
71c6d9fa94 | ||
|
|
c010665ea9 | ||
|
|
6d94ce7e5c | ||
|
|
d6fba12cd9 | ||
|
|
182a8d59ab | ||
|
|
f1847ef650 | ||
|
|
6ea1c291e6 | ||
|
|
05d5c96491 | ||
|
|
35a566cbe5 | ||
|
|
f12e40e4c6 | ||
|
|
bb4e671c83 | ||
|
|
d1637d2432 | ||
|
|
1c79628613 | ||
|
|
521a7084b7 | ||
|
|
77268d5f5b | ||
|
|
1b3abf17ec | ||
|
|
3cfd445894 | ||
|
|
b510eec9aa | ||
|
|
e5eac0933f | ||
|
|
a3c71fdfc0 | ||
|
|
78750d3d96 | ||
|
|
90edf94fee | ||
|
|
065fb72677 | ||
|
|
ddc6141480 | ||
|
|
7bc137fa79 | ||
|
|
dafe9de753 | ||
|
|
78a8474a24 | ||
|
|
123066f1cd | ||
|
|
a153cca3c0 | ||
|
|
35c7c32c8e | ||
|
|
6d6b64ebc3 | ||
|
|
c954695d3c | ||
|
|
338d8e2136 | ||
|
|
6674206b4f | ||
|
|
c46ad521d1 | ||
|
|
66e5bec4d8 | ||
|
|
0d3161ef84 | ||
|
|
1cd11bdc18 | ||
|
|
9cce2696e2 | ||
|
|
508d32d2d9 | ||
|
|
cccc307862 | ||
|
|
0772139a60 | ||
|
|
70028c8be6 | ||
|
|
3dc22bfdab | ||
|
|
f3ee421030 |
@@ -1,346 +0,0 @@
|
||||
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,6 +2620,431 @@
|
||||
"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",
|
||||
@@ -3351,6 +3776,166 @@
|
||||
"@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",
|
||||
@@ -3391,6 +3976,46 @@
|
||||
"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",
|
||||
@@ -8635,9 +9260,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -12507,21 +13132,6 @@
|
||||
"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",
|
||||
@@ -16833,6 +17443,21 @@
|
||||
"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 (currentEula && !currentUser.eulaIsAccepted) {
|
||||
if (!isPartsEntry && currentEula && !currentUser.eulaIsAccepted) {
|
||||
return <Eula />;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ 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,
|
||||
@@ -450,7 +451,9 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
||||
setEnterAgain(false);
|
||||
}}
|
||||
>
|
||||
<BillFormContainer form={form} disableInvNumber={billEnterModal.context.disableInvNumber} />
|
||||
<RbacWrapper action="bills:enter">
|
||||
<BillFormContainer form={form} disableInvNumber={billEnterModal.context.disableInvNumber} />
|
||||
</RbacWrapper>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -19,30 +19,35 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
openChatByPhone: (phone) => dispatch(openChatByPhone(phone))
|
||||
});
|
||||
|
||||
export function ChatOpenButton({ bodyshop, searchingForConversation, phone, jobid, openChatByPhone }) {
|
||||
export function ChatOpenButton({ bodyshop, searchingForConversation, phone, type, jobid, openChatByPhone }) {
|
||||
const { t } = useTranslation();
|
||||
const { socket } = useSocket();
|
||||
const notification = useNotification();
|
||||
|
||||
if (!phone) return <></>;
|
||||
|
||||
if (!bodyshop.messagingservicesid) return <PhoneNumberFormatter>{phone}</PhoneNumberFormatter>;
|
||||
if (!bodyshop.messagingservicesid) {
|
||||
return <PhoneNumberFormatter type={type}>{phone}</PhoneNumberFormatter>;
|
||||
}
|
||||
|
||||
return (
|
||||
<a
|
||||
href="# "
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (searchingForConversation) return; // Prevent finding the same thing twice.
|
||||
|
||||
const p = parsePhoneNumber(phone, "CA");
|
||||
if (searchingForConversation) return; //This is to prevent finding the same thing twice.
|
||||
if (p && p.isValid()) {
|
||||
openChatByPhone({ phone_num: p.formatInternational(), jobid: jobid, socket });
|
||||
openChatByPhone({ phone_num: p.formatInternational(), jobid, socket });
|
||||
} else {
|
||||
notification["error"]({ message: t("messaging.error.invalidphone") });
|
||||
}
|
||||
}}
|
||||
>
|
||||
<PhoneNumberFormatter>{phone}</PhoneNumberFormatter>
|
||||
<PhoneNumberFormatter type={type}>{phone}</PhoneNumberFormatter>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -133,6 +133,9 @@ export function ContractConvertToRo({ bodyshop, currentUser, contract, disabled
|
||||
ownr_ln: contract.job.owner.ownr_ln,
|
||||
ownr_co_nm: contract.job.owner.ownr_co_nm,
|
||||
ownr_ph1: contract.job.owner.ownr_ph1,
|
||||
ownr_ph2: contract.job.owner.ownr_ph2,
|
||||
ownr_ph1_ty: contract.job.owner.ownr_ph1_ty,
|
||||
ownr_ph2_ty: contract.job.owner.ownr_ph2_ty,
|
||||
ownr_ea: contract.job.owner.ownr_ea,
|
||||
v_model_desc: contract.job.vehicle && contract.job.vehicle.v_model_desc,
|
||||
v_model_yr: contract.job.vehicle && contract.job.vehicle.v_model_yr,
|
||||
|
||||
@@ -142,17 +142,37 @@ export default function JobLifecycleDashboardComponent({ data, bodyshop, ...card
|
||||
title={t("job_lifecycle.content.legend_title")}
|
||||
style={{ marginTop: "10px" }}
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: 8
|
||||
}}
|
||||
>
|
||||
{lifecycleData.summations.map((key) => (
|
||||
<Tag key={key.status} color={key.color} style={{ width: "13vh", padding: "4px", margin: "4px" }}>
|
||||
<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"
|
||||
}}
|
||||
>
|
||||
<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",
|
||||
textAlign: "center"
|
||||
padding: "4px 8px",
|
||||
textAlign: "center",
|
||||
whiteSpace: "nowrap" // keep it on one line while letting the pill expand
|
||||
}}
|
||||
>
|
||||
{key.status} [{lifecycleData.statusCounts[key.status]}] ({key.roundedPercentage})
|
||||
|
||||
@@ -256,9 +256,9 @@ export default function DashboardScheduledDeliveryToday({ data, ...cardProps })
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<Space size="small" wrap>
|
||||
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
|
||||
<ChatOpenButton type={record.ownr_ph1_ty} phone={record.ownr_ph1} jobid={record.jobid} />
|
||||
|
||||
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
|
||||
<ChatOpenButton type={record.ownr_ph2_ty} phone={record.ownr_ph2} jobid={record.jobid} />
|
||||
</Space>
|
||||
)
|
||||
},
|
||||
@@ -397,6 +397,8 @@ export const DashboardScheduledDeliveryTodayGql = `
|
||||
ownr_ln
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ph1_ty
|
||||
ownr_ph2_ty
|
||||
production_vars
|
||||
ro_number
|
||||
scheduled_delivery
|
||||
|
||||
@@ -48,6 +48,8 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
ownr_ln: item.job.ownr_ln,
|
||||
ownr_ph1: item.job.ownr_ph1,
|
||||
ownr_ph2: item.job.ownr_ph2,
|
||||
ownr_ph1_ty: item.job.ownr_ph1_ty,
|
||||
ownr_ph2_ty: item.job.ownr_ph2_ty,
|
||||
production_vars: item.job.production_vars,
|
||||
ro_number: item.job.ro_number,
|
||||
suspended: item.job.suspended,
|
||||
@@ -264,8 +266,8 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<Space size="small" wrap>
|
||||
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
|
||||
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
|
||||
<ChatOpenButton type={record.ownr_ph1_ty} phone={record.ownr_ph1} jobid={record.jobid} />
|
||||
<ChatOpenButton type={record.ownr_ph2_ty} phone={record.ownr_ph2} jobid={record.jobid} />
|
||||
</Space>
|
||||
)
|
||||
},
|
||||
@@ -400,6 +402,8 @@ export const DashboardScheduledInTodayGql = `
|
||||
ownr_ln
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ph1_ty
|
||||
ownr_ph2_ty
|
||||
production_vars
|
||||
ro_number
|
||||
suspended
|
||||
|
||||
@@ -256,9 +256,9 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<Space size="small" wrap>
|
||||
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
|
||||
<ChatOpenButton type={record.ownr_ph1_ty} phone={record.ownr_ph1} jobid={record.jobid} />
|
||||
|
||||
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
|
||||
<ChatOpenButton type={record.ownr_ph2_ty} phone={record.ownr_ph2} jobid={record.jobid} />
|
||||
</Space>
|
||||
)
|
||||
},
|
||||
@@ -397,6 +397,8 @@ export const DashboardScheduledOutTodayGql = `
|
||||
ownr_ln
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ph1_ty
|
||||
ownr_ph2_ty
|
||||
production_vars
|
||||
ro_number
|
||||
scheduled_completion
|
||||
|
||||
@@ -7,8 +7,6 @@ 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
|
||||
@@ -24,28 +22,13 @@ 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 (Fortellis.treatment === "on") {
|
||||
wsssocket.emit("fortellis-calculate-allocations", jobId, (ack) => {
|
||||
if (socket.connected) {
|
||||
socket.emit("cdk-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]);
|
||||
|
||||
@@ -93,12 +76,8 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId, title }) {
|
||||
extra={
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (Fortellis.treatment === "on") {
|
||||
socket.emit("fortellis-calculate-allocations", jobId, (ack) => setAllocationsSummary(ack));
|
||||
} else {
|
||||
socket.emit("cdk-calculate-allocations", jobId, (ack) => setAllocationsSummary(ack));
|
||||
}
|
||||
}}
|
||||
socket.emit("cdk-calculate-allocations", jobId, (ack) => setAllocationsSummary(ack));
|
||||
}}
|
||||
>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
|
||||
@@ -5,7 +5,6 @@ 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,
|
||||
@@ -19,26 +18,15 @@ 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 () => {
|
||||
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(true);
|
||||
await axios.post("/cdk/getvehicles", {
|
||||
cdk_dealerid: bodyshop.cdk_dealerid,
|
||||
bodyshopid: bodyshop.id
|
||||
});
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Button, Checkbox, Col, Table } from "antd";
|
||||
import { useEffect, useState } from "react";
|
||||
import { 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";
|
||||
@@ -17,125 +15,42 @@ const mapDispatchToProps = () => ({
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DmsCustomerSelector);
|
||||
|
||||
export function DmsCustomerSelector({ bodyshop, jobid }) {
|
||||
export function DmsCustomerSelector({ bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
const [customerList, setcustomerList] = useState([]);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [selectedCustomer, setSelectedCustomer] = useState(null);
|
||||
const [dmsType, setDmsType] = useState("cdk");
|
||||
|
||||
const {
|
||||
treatments: { Fortellis }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Fortellis"],
|
||||
splitKey: bodyshop.imexshopid
|
||||
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 { 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);
|
||||
if (Fortellis.treatment === "on") {
|
||||
wsssocket.emit(`fortellis-selected-customer`, { selectedCustomerId: selectedCustomer, jobid });
|
||||
} else {
|
||||
socket.emit(`${dmsType}-selected-customer`, selectedCustomer);
|
||||
}
|
||||
socket.emit(`${dmsType}-selected-customer`, selectedCustomer);
|
||||
setSelectedCustomer(null);
|
||||
};
|
||||
|
||||
const onUseGeneric = () => {
|
||||
setOpen(false);
|
||||
|
||||
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);
|
||||
}
|
||||
socket.emit(`${dmsType}-selected-customer`, bodyshop.cdk_configuration.generic_customer_number);
|
||||
setSelectedCustomer(null);
|
||||
};
|
||||
|
||||
const onCreateNew = () => {
|
||||
setOpen(false);
|
||||
|
||||
if (Fortellis.treatment === "on") {
|
||||
wsssocket.emit(`fortellis-selected-customer`, { selectedCustomerId: null, jobid });
|
||||
} else {
|
||||
socket.emit(`${dmsType}-selected-customer`, null);
|
||||
}
|
||||
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"),
|
||||
@@ -202,13 +117,13 @@ export function DmsCustomerSelector({ bodyshop, jobid }) {
|
||||
</div>
|
||||
)}
|
||||
pagination={{ position: "top" }}
|
||||
columns={dmsType === "cdk" ? (Fortellis.treatment === "on" ? fortellisColumns : cdkColumns) : pbsColumns}
|
||||
rowKey={(record) => (dmsType === "cdk" ? record.id?.value || record.customerId : record.ContactId)}
|
||||
columns={dmsType === "cdk" ? cdkColumns : pbsColumns}
|
||||
rowKey={(record) => (dmsType === "cdk" ? record.id.value : record.ContactId)}
|
||||
dataSource={customerList}
|
||||
//onChange={handleTableChange}
|
||||
rowSelection={{
|
||||
onSelect: (record) => {
|
||||
setSelectedCustomer(dmsType === "cdk" ? record.id?.value || record.customerId : record.ContactId);
|
||||
setSelectedCustomer(dmsType === "cdk" ? record.id.value : record.ContactId);
|
||||
},
|
||||
type: "radio",
|
||||
selectedRowKeys: [selectedCustomer]
|
||||
|
||||
@@ -26,8 +26,6 @@ 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
|
||||
@@ -38,17 +36,8 @@ 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({
|
||||
@@ -69,22 +58,13 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
|
||||
};
|
||||
|
||||
const handleFinish = (values) => {
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
socket.emit(`${determineDmsType(bodyshop)}-export-job`, {
|
||||
jobid: job.id,
|
||||
txEnvelope: values
|
||||
});
|
||||
console.log(logsRef);
|
||||
if (logsRef) {
|
||||
console.log("executing", logsRef);
|
||||
logsRef.curent &&
|
||||
logsRef.current.scrollIntoView({
|
||||
behavior: "smooth"
|
||||
@@ -189,7 +169,7 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<Space>
|
||||
<DmsCdkMakes form={form} job={job} />
|
||||
<DmsCdkMakes form={form} socket={socket} job={job} />
|
||||
<DmsCdkMakesRefetch />
|
||||
<Form.Item name="dms_unsold" label={t("jobs.fields.dms.dms_unsold")} initialValue={false}>
|
||||
<Switch />
|
||||
|
||||
@@ -55,7 +55,8 @@ const Eula = ({ currentEula, currentUser, acceptEula }) => {
|
||||
const useremail = currentUser.email;
|
||||
|
||||
try {
|
||||
const { ...otherFormValues } = formValues;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { accepted_terms, ...otherFormValues } = formValues;
|
||||
|
||||
// Trim the values of the fields before submitting
|
||||
const trimmedFormValues = Object.entries(otherFormValues).reduce((acc, [key, value]) => {
|
||||
|
||||
@@ -73,8 +73,8 @@ export default function GlobalSearchOs() {
|
||||
<span>
|
||||
<OwnerNameDisplay ownerObject={owner} />
|
||||
</span>
|
||||
<PhoneNumberFormatter>{owner.ownr_ph1}</PhoneNumberFormatter>
|
||||
<PhoneNumberFormatter>{owner.ownr_ph2}</PhoneNumberFormatter>
|
||||
<PhoneNumberFormatter type={owner?.ownr_ph1_ty}>{owner.ownr_ph1}</PhoneNumberFormatter>
|
||||
<PhoneNumberFormatter type={owner?.ownr_ph2_ty}>{owner.ownr_ph2}</PhoneNumberFormatter>
|
||||
</Space>
|
||||
</Link>
|
||||
)
|
||||
|
||||
@@ -63,8 +63,8 @@ export default function GlobalSearch() {
|
||||
<span>
|
||||
<OwnerNameDisplay ownerObject={owner} />
|
||||
</span>
|
||||
<PhoneNumberFormatter>{owner.ownr_ph1}</PhoneNumberFormatter>
|
||||
<PhoneNumberFormatter>{owner.ownr_ph2}</PhoneNumberFormatter>
|
||||
<PhoneNumberFormatter type={owner.ownr_ph1_ty}>{owner.ownr_ph1}</PhoneNumberFormatter>
|
||||
<PhoneNumberFormatter type={owner.ownr_ph2_ty}>{owner.ownr_ph2}</PhoneNumberFormatter>
|
||||
</Space>
|
||||
</Link>
|
||||
)
|
||||
|
||||
@@ -220,10 +220,10 @@ export function ScheduleEventComponent({
|
||||
</DataLabel>
|
||||
<DataLabel label={t("jobs.fields.ownr_ea")}>{(event.job && event.job.ownr_ea) || ""}</DataLabel>
|
||||
<DataLabel label={t("jobs.fields.ownr_ph1")}>
|
||||
<ChatOpenButton phone={event.job && event.job.ownr_ph1} jobid={event.job.id} />
|
||||
<ChatOpenButton phone={event?.job?.ownr_ph1} type={event?.job?.ownr_ph1_ty} jobid={event.job.id} />
|
||||
</DataLabel>
|
||||
<DataLabel label={t("jobs.fields.ownr_ph2")}>
|
||||
<ChatOpenButton phone={event.job && event.job.ownr_ph2} jobid={event.job.id} />
|
||||
<ChatOpenButton phone={event?.job?.ownr_ph2} type={event?.job?.ownr_ph2_ty} jobid={event.job.id} />
|
||||
</DataLabel>
|
||||
<DataLabel hideIfNull label={t("jobs.fields.loss_of_use")}>
|
||||
{(event.job && event.job.loss_of_use) || ""}
|
||||
|
||||
@@ -222,17 +222,37 @@ export function JobLifecycleComponent({ bodyshop, job, statuses }) {
|
||||
</div>
|
||||
</BlurWrapperComponent>
|
||||
<Card type="inner" title={t("job_lifecycle.content.legend_title")} style={{ marginTop: "10px" }}>
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: 8
|
||||
}}
|
||||
>
|
||||
{lifecycleData.durations.summations.map((key) => (
|
||||
<Tag key={key.status} color={key.color} style={{ width: "13vh", padding: "4px", margin: "4px" }}>
|
||||
<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"
|
||||
}}
|
||||
>
|
||||
<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",
|
||||
textAlign: "center"
|
||||
padding: "4px 8px",
|
||||
textAlign: "center",
|
||||
whiteSpace: "nowrap" // single line; tag gets wider instead of text escaping
|
||||
}}
|
||||
>
|
||||
{key.status} (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Form, Input } from "antd";
|
||||
import { Form, Input, Select } from "antd";
|
||||
import { useContext } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import JobCreateContext from "../../pages/jobs-create/jobs-create.context";
|
||||
@@ -7,6 +7,11 @@ import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-forma
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
export default function JobsCreateOwnerInfoNewComponent() {
|
||||
const PHONE_TYPE_OPTIONS = [
|
||||
{ label: t("owners.labels.home"), value: "Home" },
|
||||
{ label: t("owners.labels.work"), value: "Work" },
|
||||
{ label: t("owners.labels.cell"), value: "Cell" }
|
||||
];
|
||||
const [state] = useContext(JobCreateContext);
|
||||
|
||||
const { t } = useTranslation();
|
||||
@@ -105,26 +110,56 @@ export default function JobsCreateOwnerInfoNewComponent() {
|
||||
]}
|
||||
name={["owner", "data", "ownr_ea"]}
|
||||
>
|
||||
<FormItemEmail
|
||||
//email={form.getFieldValue("ownr_ea")}
|
||||
disabled={!state.owner.new}
|
||||
/>
|
||||
<FormItemEmail disabled={!state.owner.new} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("owners.fields.ownr_ph1")}
|
||||
name={["owner", "data", "ownr_ph1"]}
|
||||
rules={[({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, "owner.data.ownr_ph1")]}
|
||||
>
|
||||
<FormItemPhone disabled={!state.owner.new} />
|
||||
|
||||
{/* Phone 1 + Type */}
|
||||
<Form.Item label={t("owners.fields.ownr_ph1")} style={{ marginBottom: 0 }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||
<Form.Item
|
||||
name={["owner", "data", "ownr_ph1"]}
|
||||
noStyle
|
||||
rules={[({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, "owner.data.ownr_ph1")]}
|
||||
>
|
||||
<FormItemPhone disabled={!state.owner.new} style={{ flex: 1, minWidth: 150 }} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name={["owner", "data", "ownr_ph1_ty"]} noStyle>
|
||||
<Select
|
||||
disabled={!state.owner.new}
|
||||
allowClear
|
||||
placeholder="Type"
|
||||
options={PHONE_TYPE_OPTIONS}
|
||||
style={{ width: 110 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("owners.fields.ownr_ph2")}
|
||||
name={["owner", "data", "ownr_ph2"]}
|
||||
rules={[({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, "owner.data.ownr_ph2")]}
|
||||
>
|
||||
<FormItemPhone disabled={!state.owner.new} />
|
||||
|
||||
{/* Phone 2 + Type */}
|
||||
<Form.Item label={t("owners.fields.ownr_ph2")} style={{ marginBottom: 0 }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||
<Form.Item
|
||||
name={["owner", "data", "ownr_ph2"]}
|
||||
noStyle
|
||||
rules={[({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, "owner.data.ownr_ph2")]}
|
||||
>
|
||||
<FormItemPhone disabled={!state.owner.new} style={{ flex: 1, minWidth: 150 }} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name={["owner", "data", "ownr_ph2_ty"]} noStyle>
|
||||
<Select
|
||||
disabled={!state.owner.new}
|
||||
allowClear
|
||||
placeholder="Type"
|
||||
options={PHONE_TYPE_OPTIONS}
|
||||
style={{ width: 110 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
|
||||
<LayoutFormRow grow>
|
||||
<Form.Item label={t("owners.fields.preferred_contact")} name={["owner", "data", "preferred_contact"]}>
|
||||
<Input disabled={!state.owner.new} />
|
||||
|
||||
@@ -61,7 +61,7 @@ export default function JobsCreateOwnerInfoSearchComponent({ loading, owners })
|
||||
title: t("owners.fields.ownr_ph1"),
|
||||
dataIndex: "ownr_ph1",
|
||||
key: "ownr_ph1",
|
||||
render: (text, record) => <PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>,
|
||||
render: (text, record) => <PhoneFormatter type={record.ownr_ph1_ty}>{record.ownr_ph1}</PhoneFormatter>,
|
||||
sorter: (a, b) => alphaSort(a.ownr_ph1, b.ownr_ph1),
|
||||
sortOrder: tableState.sortedInfo.columnKey === "ownr_ph1" && tableState.sortedInfo.order
|
||||
},
|
||||
@@ -69,7 +69,7 @@ export default function JobsCreateOwnerInfoSearchComponent({ loading, owners })
|
||||
title: t("owners.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
render: (text, record) => <PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>,
|
||||
render: (text, record) => <PhoneFormatter type={record.ownr_ph2_ty}>{record.ownr_ph2}</PhoneFormatter>,
|
||||
sorter: (a, b) => alphaSort(a.ownr_ph2, b.ownr_ph2),
|
||||
sortOrder: tableState.sortedInfo.columnKey === "ownr_ph2" && tableState.sortedInfo.order
|
||||
}
|
||||
|
||||
@@ -609,7 +609,7 @@ export function JobsDetailHeaderActions({
|
||||
<FormDateTimePickerComponent
|
||||
onBlur={() => {
|
||||
const start = form.getFieldValue("start");
|
||||
form.setFieldsValue({ end: start.add(30, "minutes") });
|
||||
form.setFieldsValue({ end: start?.add(30, "minutes") });
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -251,16 +251,16 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, is
|
||||
<div>
|
||||
<DataLabel key="2" label={t("jobs.fields.ownr_ph1")}>
|
||||
{disabled || isPartsEntry ? (
|
||||
<PhoneNumberFormatter>{job.ownr_ph1}</PhoneNumberFormatter>
|
||||
<PhoneNumberFormatter type={job.ownr_ph1_ty}>{job.ownr_ph1}</PhoneNumberFormatter>
|
||||
) : (
|
||||
<ChatOpenButton phone={job.ownr_ph1} jobid={job.id} />
|
||||
<ChatOpenButton type={job.ownr_ph1_ty} phone={job.ownr_ph1} jobid={job.id} />
|
||||
)}
|
||||
</DataLabel>
|
||||
<DataLabel key="22" label={t("jobs.fields.ownr_ph2")}>
|
||||
{disabled || isPartsEntry ? (
|
||||
<PhoneNumberFormatter>{job.ownr_ph2}</PhoneNumberFormatter>
|
||||
<PhoneNumberFormatter type={job.ownr_ph2_ty}>{job.ownr_ph2}</PhoneNumberFormatter>
|
||||
) : (
|
||||
<ChatOpenButton phone={job.ownr_ph2} jobid={job.id} />
|
||||
<ChatOpenButton type={job.ownr_ph2_ty} phone={job.ownr_ph2} jobid={job.id} />
|
||||
)}
|
||||
</DataLabel>
|
||||
<DataLabel key="3" label={t("owners.fields.address")}>
|
||||
|
||||
@@ -35,16 +35,14 @@ export function JobsDocumentsImgproxyDownloadButton({ galleryImages, identifier,
|
||||
...galleryImages.other.filter((image) => image.isSelected)
|
||||
];
|
||||
|
||||
function downloadProgress(progressEvent) {
|
||||
setDownload((currentDownloadState) => {
|
||||
return {
|
||||
downloaded: progressEvent.loaded || 0,
|
||||
speed: (progressEvent.loaded || 0) - ((currentDownloadState && currentDownloadState.downloaded) || 0)
|
||||
};
|
||||
});
|
||||
}
|
||||
const downloadProgress = ({ loaded }) => {
|
||||
setDownload((currentDownloadState) => ({
|
||||
downloaded: loaded ?? 0,
|
||||
speed: (loaded ?? 0) - (currentDownloadState?.downloaded ?? 0)
|
||||
}));
|
||||
};
|
||||
|
||||
function standardMediaDownload(bufferData) {
|
||||
const standardMediaDownload = (bufferData) => {
|
||||
try {
|
||||
const a = document.createElement("a");
|
||||
const url = window.URL.createObjectURL(new Blob([bufferData]));
|
||||
@@ -55,29 +53,26 @@ export function JobsDocumentsImgproxyDownloadButton({ galleryImages, identifier,
|
||||
setLoading(false);
|
||||
setDownload(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
logImEXEvent("jobs_documents_download");
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await axios({
|
||||
const { data } = await axios({
|
||||
url: "/media/imgproxy/download",
|
||||
method: "POST",
|
||||
responseType: "blob",
|
||||
data: { jobId, documentids: imagesToDownload.map((_) => _.id) },
|
||||
onDownloadProgress: downloadProgress
|
||||
});
|
||||
|
||||
setLoading(false);
|
||||
setDownload(null);
|
||||
|
||||
// Use the response data (Blob) to trigger download
|
||||
standardMediaDownload(response.data);
|
||||
standardMediaDownload(data);
|
||||
} catch {
|
||||
// handle error (optional)
|
||||
} finally {
|
||||
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 disabled={imagesToDelete.length < 1} loading={loading}>
|
||||
<Button danger 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>
|
||||
<JobsDocumentsLocalGalleryReassign jobid={job.id} />
|
||||
<JobsDocumentsLocalGallerySelectAllComponent jobid={job.id} />
|
||||
<JobsDocumentsLocalGalleryReassign jobid={job.id} />
|
||||
<JobsLocalGalleryDownloadButton job={job} />
|
||||
<JobsDocumentsLocalDeleteButton jobid={job.id} />
|
||||
</Space>
|
||||
|
||||
@@ -28,6 +28,8 @@ 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);
|
||||
@@ -36,7 +38,7 @@ export function JobsDocumentsLocalDeleteButton({ bodyshop, getJobMedia, allMedia
|
||||
`${bodyshop.localmediaserverhttp}/jobs/delete`,
|
||||
{
|
||||
jobid: jobid,
|
||||
files: (allMedia?.[jobid] || []).filter((i) => i.isSelected).map((i) => i.filename)
|
||||
files: imagesToDelete.map((i) => i.filename)
|
||||
},
|
||||
{ headers: { ims_token: bodyshop.localmediatoken } }
|
||||
);
|
||||
@@ -60,14 +62,17 @@ 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={{ type: "danger" }}
|
||||
okButtonProps={{ danger: true }}
|
||||
cancelText={t("general.actions.cancel")}
|
||||
>
|
||||
<Button loading={loading}>{t("documents.actions.delete")}</Button>
|
||||
<Button danger disabled={imagesToDelete.length < 1} loading={loading}>
|
||||
{t("documents.actions.delete")}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Button } from "antd";
|
||||
import { Button, Space } 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,45 +19,63 @@ export default connect(mapStateToProps, mapDispatchToProps)(JobsLocalGalleryDown
|
||||
|
||||
export function JobsLocalGalleryDownloadButton({ bodyshop, allMedia, job }) {
|
||||
const { t } = useTranslation();
|
||||
const [download, setDownload] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [download, setDownload] = useState(false);
|
||||
|
||||
function downloadProgress(progressEvent) {
|
||||
setDownload((currentDownloadState) => {
|
||||
return {
|
||||
downloaded: progressEvent.loaded || 0,
|
||||
speed: (progressEvent.loaded || 0) - (currentDownloadState?.downloaded || 0)
|
||||
};
|
||||
});
|
||||
}
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
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);
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Button loading={!!download} onClick={handleDownload}>
|
||||
{t("documents.actions.download")}
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -64,7 +64,11 @@ export default function JobsFindModalComponent({
|
||||
width: "12%",
|
||||
ellipsis: true,
|
||||
render: (text, record) => {
|
||||
return record.ownr_ph1 ? <PhoneFormatter>{record.ownr_ph1}</PhoneFormatter> : t("general.labels.unknown");
|
||||
return record.ownr_ph1 ? (
|
||||
<PhoneFormatter type={record.ownr_ph1_ty}>{record.ownr_ph1}</PhoneFormatter>
|
||||
) : (
|
||||
t("general.labels.unknown")
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -74,7 +78,11 @@ export default function JobsFindModalComponent({
|
||||
width: "12%",
|
||||
ellipsis: true,
|
||||
render: (text, record) => {
|
||||
return record.ownr_ph2 ? <PhoneFormatter>{record.ownr_ph2}</PhoneFormatter> : t("general.labels.unknown");
|
||||
return record.ownr_ph2 ? (
|
||||
<PhoneFormatter type={record.ownr_ph2_ty}>{record.ownr_ph2}</PhoneFormatter>
|
||||
) : (
|
||||
t("general.labels.unknown")
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -245,7 +253,11 @@ export default function JobsFindModalComponent({
|
||||
>
|
||||
{t("jobs.labels.override_header")}
|
||||
</Checkbox>
|
||||
<Checkbox checked={partsQueueToggle} onChange={(e) => setPartsQueueToggle(e.target.checked)} id="parts_queue_toggle">
|
||||
<Checkbox
|
||||
checked={partsQueueToggle}
|
||||
onChange={(e) => setPartsQueueToggle(e.target.checked)}
|
||||
id="parts_queue_toggle"
|
||||
>
|
||||
{t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")}
|
||||
</Checkbox>
|
||||
<Checkbox
|
||||
|
||||
@@ -72,14 +72,14 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
dataIndex: "ownr_ph1",
|
||||
key: "ownr_ph1",
|
||||
ellipsis: true,
|
||||
render: (text, record) => <StartChatButton phone={record.ownr_ph1} jobid={record.id} />
|
||||
render: (text, record) => <StartChatButton type={record.ownr_ph1_ty} phone={record.ownr_ph1} jobid={record.id} />
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
ellipsis: true,
|
||||
render: (text, record) => <StartChatButton phone={record.ownr_ph2} jobid={record.id} />
|
||||
render: (text, record) => <StartChatButton type={record.ownr_ph2_ty} phone={record.ownr_ph2} jobid={record.id} />
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.status"),
|
||||
|
||||
@@ -139,7 +139,7 @@ export function JobsList({ bodyshop }) {
|
||||
key: "ownr_ph1",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => <ChatOpenButton phone={record.ownr_ph1} jobid={record.id} />
|
||||
render: (text, record) => <ChatOpenButton type={record.ownr_ph1_ty} phone={record.ownr_ph1} jobid={record.id} />
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph2"),
|
||||
@@ -147,7 +147,7 @@ export function JobsList({ bodyshop }) {
|
||||
key: "ownr_ph2",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => <ChatOpenButton phone={record.ownr_ph2} jobid={record.id} />
|
||||
render: (text, record) => <ChatOpenButton type={record.ownr_ph2_ty} phone={record.ownr_ph2} jobid={record.id} />
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
@@ -140,7 +140,7 @@ export function JobsReadyList({ bodyshop }) {
|
||||
key: "ownr_ph1",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => <ChatOpenButton phone={record.ownr_ph1} jobid={record.id} />
|
||||
render: (text, record) => <ChatOpenButton type={record.ownr_ph1_ty} phone={record.ownr_ph1} jobid={record.id} />
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph2"),
|
||||
@@ -148,7 +148,7 @@ export function JobsReadyList({ bodyshop }) {
|
||||
key: "ownr_ph2",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => <ChatOpenButton phone={record.ownr_ph2} jobid={record.id} />
|
||||
render: (text, record) => <ChatOpenButton type={record.ownr_ph2_ty} phone={record.ownr_ph2} jobid={record.id} />
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.status"),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Form, Input, Tooltip } from "antd";
|
||||
import { Form, Input, Select, Tooltip } from "antd";
|
||||
import { CloseCircleFilled } from "@ant-design/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
@@ -10,6 +10,12 @@ export default function OwnerDetailFormComponent({ form, isPhone1OptedOut, isPho
|
||||
const { t } = useTranslation();
|
||||
const { getFieldValue } = form;
|
||||
|
||||
const PHONE_TYPE_OPTIONS = [
|
||||
{ label: t("owners.labels.home"), value: "Home" },
|
||||
{ label: t("owners.labels.work"), value: "Work" },
|
||||
{ label: t("owners.labels.cell"), value: "Cell" }
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormFieldsChanged form={form} />
|
||||
@@ -30,6 +36,7 @@ export default function OwnerDetailFormComponent({ form, isPhone1OptedOut, isPho
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
|
||||
<LayoutFormRow header={t("owners.forms.address")}>
|
||||
<Form.Item label={t("owners.fields.ownr_addr1")} name="ownr_addr1">
|
||||
<Input />
|
||||
@@ -50,6 +57,7 @@ export default function OwnerDetailFormComponent({ form, isPhone1OptedOut, isPho
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
|
||||
<LayoutFormRow header={t("owners.forms.contact")}>
|
||||
<Form.Item
|
||||
label={t("owners.fields.ownr_ea")}
|
||||
@@ -63,6 +71,8 @@ export default function OwnerDetailFormComponent({ form, isPhone1OptedOut, isPho
|
||||
>
|
||||
<FormItemEmail email={getFieldValue("ownr_ea")} />
|
||||
</Form.Item>
|
||||
|
||||
{/* Phone 1 + Type + Opt-out icon */}
|
||||
<Form.Item label={t("owners.fields.ownr_ph1")} style={{ marginBottom: 0 }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
<Form.Item
|
||||
@@ -72,6 +82,11 @@ export default function OwnerDetailFormComponent({ form, isPhone1OptedOut, isPho
|
||||
>
|
||||
<Input style={{ flex: 1, minWidth: "150px" }} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="ownr_ph1_ty" noStyle>
|
||||
<Select allowClear placeholder="Type" options={PHONE_TYPE_OPTIONS} style={{ width: 110 }} />
|
||||
</Form.Item>
|
||||
|
||||
{isPhone1OptedOut && (
|
||||
<Tooltip title={t("consent.text_body")}>
|
||||
<CloseCircleFilled
|
||||
@@ -88,6 +103,8 @@ export default function OwnerDetailFormComponent({ form, isPhone1OptedOut, isPho
|
||||
)}
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
{/* Phone 2 + Type + Opt-out icon */}
|
||||
<Form.Item label={t("owners.fields.ownr_ph2")} style={{ marginBottom: 0 }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
<Form.Item
|
||||
@@ -97,6 +114,11 @@ export default function OwnerDetailFormComponent({ form, isPhone1OptedOut, isPho
|
||||
>
|
||||
<Input style={{ flex: 1, minWidth: "150px" }} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="ownr_ph2_ty" noStyle>
|
||||
<Select allowClear placeholder="Type" options={PHONE_TYPE_OPTIONS} style={{ width: 110 }} />
|
||||
</Form.Item>
|
||||
|
||||
{isPhone2OptedOut && (
|
||||
<Tooltip title={t("consent.text_body")}>
|
||||
<CloseCircleFilled
|
||||
@@ -113,6 +135,7 @@ export default function OwnerDetailFormComponent({ form, isPhone1OptedOut, isPho
|
||||
)}
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={t("owners.fields.preferred_contact")} name="preferred_contact">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
@@ -120,6 +143,7 @@ export default function OwnerDetailFormComponent({ form, isPhone1OptedOut, isPho
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
|
||||
<Form.Item label={t("owners.fields.note")} name="note">
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
|
||||
@@ -25,8 +25,10 @@ export default function OwnerDetailUpdateJobsComponent({ owner, selectedJobs, di
|
||||
ownr_ea: owner["ownr_ea"],
|
||||
ownr_fn: owner["ownr_fn"],
|
||||
ownr_ph1: owner["ownr_ph1"],
|
||||
ownr_ph1_ty: owner["ownr_ph1_ty"],
|
||||
ownr_ln: owner["ownr_ln"],
|
||||
ownr_ph2: owner["ownr_ph2"],
|
||||
ownr_ph2_ty: owner["ownr_ph2_ty"],
|
||||
ownr_st: owner["ownr_st"],
|
||||
ownr_title: owner["ownr_title"],
|
||||
ownr_zip: owner["ownr_zip"]
|
||||
|
||||
@@ -47,13 +47,13 @@ export default function OwnerFindModalComponent({
|
||||
title: t("owners.fields.ownr_ph1"),
|
||||
dataIndex: "ownr_ph1",
|
||||
key: "ownr_ph1",
|
||||
render: (text, record) => <PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>
|
||||
render: (text, record) => <PhoneFormatter type={record.ownr_ph1_ty}>{record.ownr_ph1}</PhoneFormatter>
|
||||
},
|
||||
{
|
||||
title: t("owners.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
render: (text, record) => <PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>
|
||||
render: (text, record) => <PhoneFormatter type={record.ownr_ph2_ty}>{record.ownr_ph2}</PhoneFormatter>
|
||||
},
|
||||
{
|
||||
title: t("owners.fields.note"),
|
||||
|
||||
@@ -15,10 +15,10 @@ export default function OwnerTagPopoverComponent({ job }) {
|
||||
<OwnerNameDisplay ownerObject={job} />
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item key="2" label={t("jobs.fields.ownr_ph1")}>
|
||||
<PhoneFormatter>{job.ownr_ph1 || ""}</PhoneFormatter>
|
||||
<PhoneFormatter type={job.ownr_ph1_ty}>{job.ownr_ph1 || ""}</PhoneFormatter>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item key="22" label={t("jobs.fields.ownr_ph2")}>
|
||||
<PhoneFormatter>{job.ownr_ph2 || ""}</PhoneFormatter>
|
||||
<PhoneFormatter type={job.ownr_ph2_ty}>{job.ownr_ph2 || ""}</PhoneFormatter>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item key="3" label={t("owners.fields.address")}>
|
||||
{`${job.ownr_addr1 || ""} ${job.ownr_addr2 || ""} ${
|
||||
@@ -36,13 +36,10 @@ export default function OwnerTagPopoverComponent({ job }) {
|
||||
<OwnerNameDisplay ownerObject={job.owner} />
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item key="2" label={t("jobs.fields.ownr_ph1")}>
|
||||
<PhoneFormatter>{job.owner.ownr_ph1 || ""}</PhoneFormatter>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item key="22" label={t("jobs.fields.ownr_ph2")}>
|
||||
<PhoneFormatter>{job.owner.ownr_ph2 || ""}</PhoneFormatter>
|
||||
<PhoneFormatter type={job.owner.ownr_ph1_ty}>{job.owner.ownr_ph1 || ""}</PhoneFormatter>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item key="2" label={t("jobs.fields.ownr_ph2")}>
|
||||
<PhoneFormatter>{job.owner.ownr_ph2 || ""}</PhoneFormatter>
|
||||
<PhoneFormatter type={job.owner.ownr_ph2_ty}>{job.owner.ownr_ph2 || ""}</PhoneFormatter>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item key="3" label={t("owners.fields.address")}>
|
||||
{`${job.owner.ownr_addr1 || ""} ${job.owner.ownr_addr2 || ""} ${
|
||||
|
||||
@@ -39,7 +39,7 @@ export default function OwnersListComponent({ loading, owners, total, refetch })
|
||||
dataIndex: "ownr_ph1",
|
||||
key: "ownr_ph1",
|
||||
render: (text, record) => {
|
||||
return <PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>;
|
||||
return <PhoneFormatter type={record.ownr_ph1_ty}>{record.ownr_ph1}</PhoneFormatter>;
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -47,7 +47,7 @@ export default function OwnersListComponent({ loading, owners, total, refetch })
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
render: (text, record) => {
|
||||
return <PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>;
|
||||
return <PhoneFormatter type={record.ownr_ph2_ty}>{record.ownr_ph2}</PhoneFormatter>;
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -255,14 +255,14 @@ const productionListColumnsData = ({ technician, state, activeStatuses, data, bo
|
||||
dataIndex: "ownr_ph1",
|
||||
key: "ownr_ph1",
|
||||
ellipsis: true,
|
||||
render: (text, record) => <PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>
|
||||
render: (text, record) => <PhoneFormatter type={record.ownr_ph1_ty}>{record.ownr_ph1}</PhoneFormatter>
|
||||
},
|
||||
{
|
||||
title: i18n.t("jobs.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
ellipsis: true,
|
||||
render: (text, record) => <PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>
|
||||
render: (text, record) => <PhoneFormatter type={record.ownr_ph2_ty}>{record.ownr_ph2}</PhoneFormatter>
|
||||
},
|
||||
{
|
||||
title: i18n.t("jobs.fields.specialcoveragepolicy"),
|
||||
|
||||
@@ -144,7 +144,7 @@ export function ProductionListEmpAssignment({ insertAuditTrail, bodyshop, record
|
||||
<Spin spinning={loading}>
|
||||
{record[type] ? (
|
||||
<div>
|
||||
<span>{`${theEmployee.first_name || ""} ${theEmployee.last_name || ""}`}</span>
|
||||
<span>{`${theEmployee?.first_name || ""} ${theEmployee?.last_name || ""}`}</span>
|
||||
<DeleteFilled style={iconStyle} onClick={() => handleRemove(type)} />
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -154,13 +154,25 @@ export function ProductionListDetail({ bodyshop, jobs, setPrintCenterContext, te
|
||||
<OwnerNameDisplay ownerObject={theJob} />
|
||||
{!technician ? (
|
||||
<>
|
||||
<StartChatButton phone={data.jobs_by_pk.ownr_ph1} jobid={data.jobs_by_pk.id} />
|
||||
<StartChatButton phone={data.jobs_by_pk.ownr_ph2} jobid={data.jobs_by_pk.id} />
|
||||
<StartChatButton
|
||||
type={data.jobs_by_pk.ownr_ph1_ty}
|
||||
phone={data.jobs_by_pk.ownr_ph1}
|
||||
jobid={data.jobs_by_pk.id}
|
||||
/>
|
||||
<StartChatButton
|
||||
type={data.jobs_by_pk.ownr_ph2_ty}
|
||||
phone={data.jobs_by_pk.ownr_ph2}
|
||||
jobid={data.jobs_by_pk.id}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<PhoneNumberFormatter>{data.jobs_by_pk.ownr_ph1}</PhoneNumberFormatter>
|
||||
<PhoneNumberFormatter>{data.jobs_by_pk.ownr_ph2}</PhoneNumberFormatter>
|
||||
<PhoneNumberFormatter type={data.jobs_by_pk.ownr_ph1_ty}>
|
||||
{data.jobs_by_pk.ownr_ph1}
|
||||
</PhoneNumberFormatter>
|
||||
<PhoneNumberFormatter type={data.jobs_by_pk.ownr_ph2_ty}>
|
||||
{data.jobs_by_pk.ownr_ph2}
|
||||
</PhoneNumberFormatter>
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
|
||||
@@ -143,7 +143,7 @@ export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) {
|
||||
|
||||
//TODO: Find a way to filter out / blur on demand.
|
||||
return (
|
||||
<div>
|
||||
<div className="report-center-modal">
|
||||
<Form onFinish={handleFinish} autoComplete={"off"} layout="vertical" form={form}>
|
||||
<Input.Search onChange={(e) => setSearch(e.target.value)} value={search} />
|
||||
<Form.Item name="defaultSorters" hidden />
|
||||
@@ -163,13 +163,14 @@ export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) {
|
||||
{Object.keys(grouped)
|
||||
//.filter((key) => !groupExcludeKeyFilter.includes(key))
|
||||
.map((key) => (
|
||||
<Col md={8} sm={12} key={key}>
|
||||
<Col xs={24} sm={12} md={Object.keys(grouped).length === 1 ? 24 : 8} key={key}>
|
||||
<Card.Grid
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
maxHeight: "33vh",
|
||||
overflowY: "scroll"
|
||||
overflowY: "scroll",
|
||||
minWidth: "200px"
|
||||
}}
|
||||
>
|
||||
<Typography.Title level={4}>{t(`reportcenter.labels.groups.${key}`)}</Typography.Title>
|
||||
@@ -177,7 +178,7 @@ export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) {
|
||||
<BlurWrapperComponent
|
||||
featureName={groupExcludeKeyFilter.find((g) => g.key === key).featureName}
|
||||
>
|
||||
<ul style={{ listStyleType: "none", columns: "2 auto" }}>
|
||||
<ul style={{ listStyleType: "none", columns: grouped[key].length > 4 ? "2 auto" : "1", padding: 0, margin: 0 }}>
|
||||
{grouped[key].map((item) => (
|
||||
<li key={item.key}>
|
||||
<Radio key={item.key} value={item.key}>
|
||||
@@ -188,7 +189,7 @@ export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) {
|
||||
</ul>
|
||||
</BlurWrapperComponent>
|
||||
) : (
|
||||
<ul style={{ listStyleType: "none", columns: "2 auto" }}>
|
||||
<ul style={{ listStyleType: "none", columns: grouped[key].length > 4 ? "2 auto" : "1", padding: 0, margin: 0 }}>
|
||||
{grouped[key].map((item) =>
|
||||
item.featureNameRestricted ? (
|
||||
<li key={item.key}>
|
||||
|
||||
@@ -11,3 +11,38 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Report center modal fixes for column layout
|
||||
.report-center-modal {
|
||||
.ant-form-item .ant-radio-group {
|
||||
width: 100%;
|
||||
|
||||
.ant-card-grid {
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
|
||||
ul {
|
||||
width: 100%;
|
||||
|
||||
li {
|
||||
margin-bottom: 8px;
|
||||
break-inside: avoid;
|
||||
page-break-inside: avoid;
|
||||
|
||||
.ant-radio-wrapper {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
|
||||
span:not(.ant-radio) {
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
hyphens: auto;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ 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={[
|
||||
{
|
||||
@@ -42,11 +43,6 @@ 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,10 +4,18 @@ 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 TemplateListGenerated = TemplateList("job");
|
||||
const allTemplates = TemplateList("job");
|
||||
const TemplateListGenerated = InstanceRenderManager({
|
||||
imex: Object.fromEntries(
|
||||
Object.entries(allTemplates).filter(([, { enhanced_payroll }]) => !enhanced_payroll)
|
||||
),
|
||||
rome: allTemplates
|
||||
});
|
||||
|
||||
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" }}>
|
||||
<Header style={{ textAlign: "center", height: "auto", overflow: "visible" }}>
|
||||
<Typography.Title style={{ color: "#fff" }}>
|
||||
{technician
|
||||
? t("tech.labels.loggedin", {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { Button, Card, Col, Form, InputNumber, Popover, Row, Select } from "antd";
|
||||
import { Button, Card, Form, InputNumber, Popover, Select, Space } from "antd";
|
||||
import axios from "axios";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -124,103 +124,12 @@ export function TechClockOffButton({
|
||||
cost_center: isShiftTicket ? "timetickets.labels.shift" : technician ? technician.cost_center : null
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
<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="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>
|
||||
) : (
|
||||
<Space direction="vertical">
|
||||
{!isShiftTicket ? (
|
||||
<div>
|
||||
<Form.Item
|
||||
name="status"
|
||||
label={t("jobs.fields.status")}
|
||||
initialValue={lineTicketData && lineTicketData.jobs_by_pk.status}
|
||||
label={t("timetickets.fields.actualhrs")}
|
||||
name="actualhrs"
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
@@ -228,35 +137,117 @@ export function TechClockOffButton({
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Select>
|
||||
{bodyshop.md_ro_statuses.production_statuses.map((item) => (
|
||||
<Select.Option key={item}></Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
<InputNumber min={0} precision={1} />
|
||||
</Form.Item>
|
||||
)}
|
||||
<Button type="primary" htmlType="submit" loading={loading}>
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
<TechJobClockoutDelete completedCallback={completedCallback} timeTicketId={timeTicketId} />
|
||||
</Col>
|
||||
{!isShiftTicket && (
|
||||
<Col span={16}>
|
||||
<LaborAllocationContainer
|
||||
jobid={jobId || null}
|
||||
loading={queryLoading}
|
||||
lineTicketData={lineTicketData}
|
||||
/>
|
||||
</Col>
|
||||
<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="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>
|
||||
) : (
|
||||
<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>
|
||||
)}
|
||||
</Row>
|
||||
<Button type="primary" htmlType="submit" loading={loading}>
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
<TechJobClockoutDelete completedCallback={completedCallback} timeTicketId={timeTicketId} />
|
||||
{!isShiftTicket && (
|
||||
<LaborAllocationContainer jobid={jobId || null} loading={queryLoading} lineTicketData={lineTicketData} />
|
||||
)}
|
||||
</Space>
|
||||
</Form>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover content={overlay} trigger="click">
|
||||
<Popover
|
||||
content={<div style={{ maxHeight: "75vh", overflowY: "auto" }}>{overlay}</div>}
|
||||
trigger="click"
|
||||
getPopupContainer={() => document.querySelector('#time-ticket-modal')}
|
||||
>
|
||||
<Button loading={loading} {...otherBtnProps}>
|
||||
{t("timetickets.actions.clockout")}
|
||||
</Button>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { getFirestore } from "@firebase/firestore";
|
||||
import { getMessaging, getToken, onMessage } from "@firebase/messaging";
|
||||
import { store } from "../redux/store";
|
||||
//import * as amplitude from '@amplitude/analytics-browser';
|
||||
import posthog from 'posthog-js'
|
||||
// import posthog from 'posthog-js'
|
||||
|
||||
const config = JSON.parse(import.meta.env.VITE_APP_FIREBASE_CONFIG);
|
||||
initializeApp(config);
|
||||
@@ -74,7 +74,6 @@ onMessage(messaging, (payload) => {
|
||||
|
||||
export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
|
||||
try {
|
||||
|
||||
const state = stateProp || store.getState();
|
||||
|
||||
const eventParams = {
|
||||
@@ -99,8 +98,7 @@ export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
|
||||
// );
|
||||
logEvent(analytics, eventName, eventParams);
|
||||
//amplitude.track(eventName, eventParams);
|
||||
posthog.capture(eventName, eventParams);
|
||||
|
||||
//posthog.capture(eventName, eventParams);
|
||||
} finally {
|
||||
//If it fails, just keep going.
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@ export const QUERY_ALL_ACTIVE_APPOINTMENTS = gql`
|
||||
ownr_fn
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ph1_ty
|
||||
ownr_ph2_ty
|
||||
ownr_ea
|
||||
clm_total
|
||||
id
|
||||
|
||||
@@ -19,7 +19,9 @@ export const QUERY_ALL_ACTIVE_JOBS_PAGINATED = gql`
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
ownr_ph1
|
||||
ownr_ph1_ty
|
||||
ownr_ph2
|
||||
ownr_ph2_ty
|
||||
ownr_ea
|
||||
ownerid
|
||||
comment
|
||||
@@ -69,6 +71,8 @@ export const QUERY_ALL_ACTIVE_JOBS = gql`
|
||||
ownr_co_nm
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ph1_ty
|
||||
ownr_ph2_ty
|
||||
ownr_ea
|
||||
ownerid
|
||||
comment
|
||||
@@ -122,6 +126,8 @@ export const QUERY_PARTS_QUEUE = gql`
|
||||
ownr_co_nm
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ph1_ty
|
||||
ownr_ph2_ty
|
||||
ownr_ea
|
||||
plate_no
|
||||
plate_st
|
||||
@@ -179,6 +185,8 @@ export const QUERY_EXACT_JOB_IN_PRODUCTION = gql`
|
||||
clm_total
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ph1_ty
|
||||
ownr_ph2_ty
|
||||
special_coverage_policy
|
||||
owner_owing
|
||||
production_vars
|
||||
@@ -249,6 +257,8 @@ export const QUERY_EXACT_JOBS_IN_PRODUCTION = gql`
|
||||
clm_total
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ph1_ty
|
||||
ownr_ph2_ty
|
||||
special_coverage_policy
|
||||
owner_owing
|
||||
production_vars
|
||||
@@ -615,6 +625,8 @@ export const GET_JOB_BY_PK = gql`
|
||||
ownr_ln
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ph1_ty
|
||||
ownr_ph2_ty
|
||||
ownr_st
|
||||
ownr_zip
|
||||
tax_number
|
||||
@@ -631,6 +643,8 @@ export const GET_JOB_BY_PK = gql`
|
||||
ownr_ln
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ph1_ty
|
||||
ownr_ph2_ty
|
||||
ownr_st
|
||||
ownr_zip
|
||||
parts_tax_rates
|
||||
@@ -714,7 +728,7 @@ export const GET_JOB_BY_PK = gql`
|
||||
v_model_yr
|
||||
v_model_desc
|
||||
v_vin
|
||||
notes(where:{pinned: {_eq: true}}, order_by: {updated_at: desc}) {
|
||||
notes(where: { pinned: { _eq: true } }, order_by: { updated_at: desc }) {
|
||||
created_at
|
||||
created_by
|
||||
critical
|
||||
@@ -830,6 +844,8 @@ export const QUERY_JOB_CARD_DETAILS = gql`
|
||||
ownr_co_nm
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ph1_ty
|
||||
ownr_ph2_ty
|
||||
comment
|
||||
ownr_ea
|
||||
ca_gst_registrant
|
||||
@@ -1000,7 +1016,6 @@ export const QUERY_JOB_CARD_DETAILS = gql`
|
||||
key
|
||||
type
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -1230,6 +1245,8 @@ export const GET_JOB_INFO_FOR_STRIPE = gql`
|
||||
ownr_co_nm
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ph1_ty
|
||||
ownr_ph2_ty
|
||||
ownr_ea
|
||||
}
|
||||
}
|
||||
@@ -1443,8 +1460,10 @@ export const QUERY_JOB_FOR_DUPE = gql`
|
||||
ownr_ln
|
||||
ownr_ph1
|
||||
ownr_ph1x
|
||||
ownr_ph1_ty
|
||||
ownr_ph2
|
||||
ownr_ph2x
|
||||
ownr_ph2_ty
|
||||
ownr_st
|
||||
ownr_title
|
||||
ownr_zip
|
||||
@@ -1690,8 +1709,10 @@ export const QUERY_ALL_JOB_FIELDS = gql`
|
||||
ownr_ln
|
||||
ownr_ph1
|
||||
ownr_ph1x
|
||||
ownr_ph1_ty
|
||||
ownr_ph2
|
||||
ownr_ph2x
|
||||
ownr_ph2_ty
|
||||
ownr_st
|
||||
ownr_title
|
||||
ownr_zip
|
||||
@@ -1829,6 +1850,8 @@ export const QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED = gql`
|
||||
ownr_co_nm
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ph1_ty
|
||||
ownr_ph2_ty
|
||||
plate_no
|
||||
plate_st
|
||||
v_vin
|
||||
@@ -1869,6 +1892,8 @@ export const QUERY_SIMPLIFIED_PARTS_PAGINATED_STATUS_FILTERED = gql`
|
||||
ownr_co_nm
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ph1_ty
|
||||
ownr_ph2_ty
|
||||
plate_no
|
||||
plate_st
|
||||
v_vin
|
||||
@@ -2117,6 +2142,8 @@ export const GET_JOB_FOR_CC_CONTRACT = gql`
|
||||
ownr_zip
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ph1_ty
|
||||
ownr_ph2_ty
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -2415,7 +2442,7 @@ export const QUERY_PARTS_QUEUE_CARD_DETAILS = gql`
|
||||
start
|
||||
status
|
||||
}
|
||||
notes(where:{pinned: {_eq: true}}, order_by: {updated_at: desc}) {
|
||||
notes(where: { pinned: { _eq: true } }, order_by: { updated_at: desc }) {
|
||||
created_at
|
||||
created_by
|
||||
critical
|
||||
@@ -2503,6 +2530,8 @@ export const QUERY_PARTS_QUEUE_CARD_DETAILS = gql`
|
||||
ownr_ln
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ph1_ty
|
||||
ownr_ph2_ty
|
||||
owner {
|
||||
id
|
||||
preferred_contact
|
||||
@@ -2600,6 +2629,8 @@ export const QUERY_JOBS_IN_PRODUCTION = gql`
|
||||
clm_total
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ph1_ty
|
||||
ownr_ph2_ty
|
||||
special_coverage_policy
|
||||
owner_owing
|
||||
production_vars
|
||||
|
||||
@@ -8,6 +8,8 @@ export const QUERY_SEARCH_OWNER_BY_IDX = gql`
|
||||
ownr_co_nm
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ph1_ty
|
||||
ownr_ph2_ty
|
||||
ownr_addr1
|
||||
ownr_addr2
|
||||
ownr_city
|
||||
@@ -57,8 +59,10 @@ export const QUERY_OWNER_BY_ID = gql`
|
||||
ownr_ea
|
||||
ownr_fn
|
||||
ownr_ph1
|
||||
ownr_ph1_ty
|
||||
ownr_ln
|
||||
ownr_ph2
|
||||
ownr_ph2_ty
|
||||
ownr_st
|
||||
ownr_title
|
||||
ownr_zip
|
||||
@@ -112,8 +116,10 @@ export const QUERY_ALL_OWNERS = gql`
|
||||
ownr_ea
|
||||
ownr_fn
|
||||
ownr_ph1
|
||||
ownr_ph1_ty
|
||||
ownr_ln
|
||||
ownr_ph2
|
||||
ownr_ph2_ty
|
||||
ownr_st
|
||||
ownr_title
|
||||
ownr_zip
|
||||
@@ -136,8 +142,10 @@ export const QUERY_ALL_OWNERS_PAGINATED = gql`
|
||||
ownr_ea
|
||||
ownr_fn
|
||||
ownr_ph1
|
||||
ownr_ph1_ty
|
||||
ownr_ln
|
||||
ownr_ph2
|
||||
ownr_ph2_ty
|
||||
ownr_st
|
||||
ownr_title
|
||||
ownr_zip
|
||||
@@ -164,8 +172,10 @@ export const QUERY_OWNER_FOR_JOB_CREATION = gql`
|
||||
ownr_ea
|
||||
ownr_fn
|
||||
ownr_ph1
|
||||
ownr_ph1_ty
|
||||
ownr_ln
|
||||
ownr_ph2
|
||||
ownr_ph2_ty
|
||||
ownr_st
|
||||
ownr_title
|
||||
ownr_zip
|
||||
|
||||
@@ -177,10 +177,12 @@ export const QUERY_PARTS_ORDER_OEC = gql`
|
||||
ownr_fax
|
||||
ownr_faxx
|
||||
ownr_ph1
|
||||
ownr_ph1_ty
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_ph1x
|
||||
ownr_ph2
|
||||
ownr_ph2_ty
|
||||
ownr_ph2x
|
||||
ownr_st
|
||||
ownr_title
|
||||
|
||||
@@ -22,6 +22,8 @@ export const GLOBAL_SEARCH_QUERY = gql`
|
||||
ownr_co_nm
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ph1_ty
|
||||
ownr_ph2_ty
|
||||
}
|
||||
search_vehicles(args: { search: $search }, limit: 25) {
|
||||
id
|
||||
|
||||
@@ -21,8 +21,6 @@ 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
|
||||
@@ -56,14 +54,6 @@ 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 },
|
||||
@@ -94,75 +84,45 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
}, [t, setBreadcrumbs, setSelectedHeader]);
|
||||
|
||||
useEffect(() => {
|
||||
if (Fortellis.treatment === "on") {
|
||||
wsssocket.emit("set-log-level", logLevel);
|
||||
|
||||
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", () => 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("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("log-event", (payload) => {
|
||||
setLogs((logs) => {
|
||||
return [...logs, payload];
|
||||
});
|
||||
});
|
||||
socket.on("export-success", (payload) => {
|
||||
notification.success({
|
||||
message: t("jobs.successes.exported")
|
||||
});
|
||||
socket.on("export-success", (payload) => {
|
||||
notification.success({
|
||||
message: t("jobs.successes.exported")
|
||||
});
|
||||
insertAuditTrail({
|
||||
jobid: payload,
|
||||
operation: AuditTrailMapping.jobexported(),
|
||||
type: "jobexported"
|
||||
});
|
||||
history("/manage/accounting/receivables");
|
||||
insertAuditTrail({
|
||||
jobid: payload,
|
||||
operation: AuditTrailMapping.jobexported(),
|
||||
type: "jobexported"
|
||||
});
|
||||
history("/manage/accounting/receivables");
|
||||
});
|
||||
|
||||
if (socket.disconnected) socket.connect();
|
||||
return () => {
|
||||
socket.removeAllListeners();
|
||||
socket.disconnect();
|
||||
};
|
||||
}
|
||||
if (socket.disconnected) socket.connect();
|
||||
return () => {
|
||||
socket.removeAllListeners();
|
||||
socket.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (loading) return <LoadingSpinner />;
|
||||
@@ -176,9 +136,6 @@ 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
|
||||
@@ -200,7 +157,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
<DmsPostForm socket={socket} jobId={jobId} job={data && data.jobs_by_pk} logsRef={logsRef} />
|
||||
</Col>
|
||||
|
||||
<DmsCustomerSelector jobid={jobId} />
|
||||
<DmsCustomerSelector />
|
||||
|
||||
<Col span={24}>
|
||||
<div ref={logsRef}>
|
||||
@@ -225,13 +182,8 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
<Button
|
||||
onClick={() => {
|
||||
setLogs([]);
|
||||
if (Fortellis.treatment === "on") {
|
||||
wsssocket.disconnect();
|
||||
wsssocket.connect();
|
||||
} else {
|
||||
socket.disconnect();
|
||||
socket.connect();
|
||||
}
|
||||
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,17 +375,31 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
||||
const isParts = yield select((state) => state.application.isPartsEntry === true);
|
||||
const instanceSeg = InstanceRenderManager({ imex: "imex", rome: "rome" });
|
||||
|
||||
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 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);
|
||||
|
||||
const regionSeg = payload.region_config ? `region:${payload.region_config}` : null;
|
||||
const segments = [instanceSeg, ...(regionSeg ? [regionSeg] : []), ...featureSegments];
|
||||
|
||||
@@ -2590,7 +2590,10 @@
|
||||
"fromclaim": "Current Claim",
|
||||
"fromowner": "Historical Owner Record",
|
||||
"relatedjobs": "Related Jobs",
|
||||
"updateowner": "Update Owner"
|
||||
"updateowner": "Update Owner",
|
||||
"work": "Work",
|
||||
"home": "Home",
|
||||
"cell": "Cell"
|
||||
},
|
||||
"successes": {
|
||||
"delete": "Owner deleted successfully.",
|
||||
@@ -3209,6 +3212,7 @@
|
||||
"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",
|
||||
|
||||
@@ -2590,7 +2590,10 @@
|
||||
"fromclaim": "",
|
||||
"fromowner": "",
|
||||
"relatedjobs": "",
|
||||
"updateowner": ""
|
||||
"updateowner": "",
|
||||
"work": "",
|
||||
"home": "",
|
||||
"cell": ""
|
||||
},
|
||||
"successes": {
|
||||
"delete": "",
|
||||
@@ -3209,6 +3212,7 @@
|
||||
"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": "",
|
||||
|
||||
@@ -2590,7 +2590,10 @@
|
||||
"fromclaim": "",
|
||||
"fromowner": "",
|
||||
"relatedjobs": "",
|
||||
"updateowner": ""
|
||||
"updateowner": "",
|
||||
"work": "",
|
||||
"home": "",
|
||||
"cell": ""
|
||||
},
|
||||
"successes": {
|
||||
"delete": "",
|
||||
@@ -3209,6 +3212,7 @@
|
||||
"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": "",
|
||||
|
||||
@@ -1,7 +1,23 @@
|
||||
//import NumberFormat from "react-number-format";
|
||||
import { Typography } from "antd";
|
||||
import parsePhoneNumber from "libphonenumber-js";
|
||||
|
||||
export default function PhoneNumberFormatter(props) {
|
||||
const p = parsePhoneNumber(props.children || "", "CA");
|
||||
return p ? <span>{p.formatNational()}</span> : null;
|
||||
const { Text } = Typography;
|
||||
|
||||
export default function PhoneNumberFormatter({ children, type }) {
|
||||
const p = parsePhoneNumber(children || "", "CA");
|
||||
if (!p) return null;
|
||||
|
||||
const phone = p.formatNational();
|
||||
|
||||
return (
|
||||
<span>
|
||||
<Text>{phone}</Text>
|
||||
{type ? (
|
||||
<>
|
||||
{" "}
|
||||
<Text type="secondary">({type})</Text>
|
||||
</>
|
||||
) : null}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1218,6 +1218,18 @@ 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"),
|
||||
|
||||
@@ -31,7 +31,8 @@ if (!import.meta.env.DEV) {
|
||||
"Module specifier, 'fs' does not start",
|
||||
"Module specifier, 'zlib' does not start with",
|
||||
"Messaging: This browser doesn't support the API's required to use the Firebase SDK.",
|
||||
"Failed to update a ServiceWorker for scope"
|
||||
"Failed to update a ServiceWorker for scope",
|
||||
"Network Error"
|
||||
],
|
||||
integrations: [
|
||||
// See docs for support of different versions of variation of react router
|
||||
|
||||
@@ -24,11 +24,13 @@ const lightningCssTargets = browserslistToTargets(
|
||||
})
|
||||
);
|
||||
|
||||
const currentDatePST = new Date()
|
||||
.toLocaleDateString("en-US", { timeZone: "America/Los_Angeles", year: "numeric", month: "2-digit", day: "2-digit" })
|
||||
.split("/")
|
||||
.reverse()
|
||||
.join("-");
|
||||
const pstFormatter = new Intl.DateTimeFormat("en-CA", {
|
||||
timeZone: "America/Los_Angeles",
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit"
|
||||
});
|
||||
const currentDatePST = pstFormatter.format(new Date());
|
||||
|
||||
const getFormattedTimestamp = () =>
|
||||
new Date().toLocaleTimeString("en-US", { hour12: true }).replace("AM", "a.m.").replace("PM", "p.m.");
|
||||
|
||||
@@ -1156,7 +1156,11 @@
|
||||
enable_manual: false
|
||||
update:
|
||||
columns:
|
||||
- imexshopid
|
||||
- timezone
|
||||
- shopname
|
||||
- notification_followers
|
||||
- state
|
||||
- md_order_statuses
|
||||
retry_conf:
|
||||
interval_sec: 10
|
||||
@@ -3680,6 +3684,7 @@
|
||||
- completed_tasks
|
||||
- converted
|
||||
- created_at
|
||||
- created_user_email
|
||||
- cust_pr
|
||||
- date_estimated
|
||||
- date_exported
|
||||
@@ -3698,6 +3703,7 @@
|
||||
- deliverchecklist
|
||||
- depreciation_taxes
|
||||
- dms_allocation
|
||||
- dms_id
|
||||
- driveable
|
||||
- employee_body
|
||||
- employee_csr
|
||||
@@ -3793,8 +3799,10 @@
|
||||
- ownr_fn
|
||||
- ownr_ln
|
||||
- ownr_ph1
|
||||
- ownr_ph1_ty
|
||||
- ownr_ph1x
|
||||
- ownr_ph2
|
||||
- ownr_ph2_ty
|
||||
- ownr_ph2x
|
||||
- ownr_st
|
||||
- ownr_title
|
||||
@@ -3956,6 +3964,7 @@
|
||||
- completed_tasks
|
||||
- converted
|
||||
- created_at
|
||||
- created_user_email
|
||||
- cust_pr
|
||||
- date_estimated
|
||||
- date_exported
|
||||
@@ -3975,6 +3984,7 @@
|
||||
- deliverchecklist
|
||||
- depreciation_taxes
|
||||
- dms_allocation
|
||||
- dms_id
|
||||
- driveable
|
||||
- employee_body
|
||||
- employee_csr
|
||||
@@ -4071,8 +4081,10 @@
|
||||
- ownr_fn
|
||||
- ownr_ln
|
||||
- ownr_ph1
|
||||
- ownr_ph1_ty
|
||||
- ownr_ph1x
|
||||
- ownr_ph2
|
||||
- ownr_ph2_ty
|
||||
- ownr_ph2x
|
||||
- ownr_st
|
||||
- ownr_title
|
||||
@@ -4245,6 +4257,7 @@
|
||||
- completed_tasks
|
||||
- converted
|
||||
- created_at
|
||||
- created_user_email
|
||||
- cust_pr
|
||||
- date_estimated
|
||||
- date_exported
|
||||
@@ -4264,6 +4277,7 @@
|
||||
- deliverchecklist
|
||||
- depreciation_taxes
|
||||
- dms_allocation
|
||||
- dms_id
|
||||
- driveable
|
||||
- employee_body
|
||||
- employee_csr
|
||||
@@ -4360,8 +4374,10 @@
|
||||
- ownr_fn
|
||||
- ownr_ln
|
||||
- ownr_ph1
|
||||
- ownr_ph1_ty
|
||||
- ownr_ph1x
|
||||
- ownr_ph2
|
||||
- ownr_ph2_ty
|
||||
- ownr_ph2x
|
||||
- ownr_st
|
||||
- ownr_title
|
||||
@@ -4634,7 +4650,7 @@
|
||||
request_transform:
|
||||
body:
|
||||
action: transform
|
||||
template: "{\r\n \"event\": {\r\n \"session_variables\": {\r\n \"x-hasura-user-id\": {{$body?.event?.session_variables?.x-hasura-user-id ?? \"Internal\"}},\r\n \"x-hasura-role\": {{$body?.event?.session_variables?.x-hasura-role ?? \"Internal\"}}\r\n }, \r\n \"op\": {{$body.event.op}},\r\n \"data\": {\r\n \"new\": {\r\n \"id\": {{$body.event.data.new.id}},\r\n \"shopid\": {{$body.event.data.new?.shopid}},\r\n \"ro_number\": {{$body.event.data.new?.ro_number}}\r\n }\r\n }\r\n },\r\n \"trigger\": {\r\n \"name\": \"notifications_jobs_autoadd\"\r\n },\r\n \"table\": {\r\n \"schema\": \"public\",\r\n \"name\": \"jobs\"\r\n }\r\n}\r\n"
|
||||
template: "{\r\n \"event\": {\r\n \"session_variables\": {\r\n \"x-hasura-user-id\": {{$body?.event?.session_variables?.x-hasura-user-id ?? \"Internal\"}},\r\n \"x-hasura-role\": {{$body?.event?.session_variables?.x-hasura-role ?? \"Internal\"}}\r\n }, \r\n \"op\": {{$body.event.op}},\r\n \"data\": {\r\n \"new\": {\r\n \"id\": {{$body.event.data.new.id}},\r\n \"shopid\": {{$body.event.data.new?.shopid}},\r\n \"ro_number\": {{$body.event.data.new?.ro_number}},\r\n \"created_user_email\": {{$body.event.data.new?.created_user_email}}\r\n }\r\n }\r\n },\r\n \"trigger\": {\r\n \"name\": \"notifications_jobs_autoadd\"\r\n },\r\n \"table\": {\r\n \"schema\": \"public\",\r\n \"name\": \"jobs\"\r\n }\r\n}\r\n"
|
||||
method: POST
|
||||
query_params: {}
|
||||
template_engine: Kriti
|
||||
@@ -5161,7 +5177,9 @@
|
||||
- ownr_fn
|
||||
- ownr_ln
|
||||
- ownr_ph1
|
||||
- ownr_ph1_ty
|
||||
- ownr_ph2
|
||||
- ownr_ph2_ty
|
||||
- ownr_st
|
||||
- ownr_title
|
||||
- ownr_zip
|
||||
@@ -5186,7 +5204,9 @@
|
||||
- ownr_fn
|
||||
- ownr_ln
|
||||
- ownr_ph1
|
||||
- ownr_ph1_ty
|
||||
- ownr_ph2
|
||||
- ownr_ph2_ty
|
||||
- ownr_st
|
||||
- ownr_title
|
||||
- ownr_zip
|
||||
@@ -5222,7 +5242,9 @@
|
||||
- ownr_fn
|
||||
- ownr_ln
|
||||
- ownr_ph1
|
||||
- ownr_ph1_ty
|
||||
- ownr_ph2
|
||||
- ownr_ph2_ty
|
||||
- ownr_st
|
||||
- ownr_title
|
||||
- ownr_zip
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
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;
|
||||
@@ -0,0 +1,5 @@
|
||||
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;
|
||||
@@ -0,0 +1,23 @@
|
||||
-- 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();
|
||||
@@ -0,0 +1,21 @@
|
||||
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();
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX IF EXISTS "public"."media_analytics_detail_bodyshopid";
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE INDEX "media_analytics_detail_bodyshopid" on
|
||||
"public"."media_analytics_detail" using btree ("bodyshopid");
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX IF EXISTS "public"."media_analytics_detail_jobid";
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE INDEX "media_analytics_detail_jobid" on
|
||||
"public"."media_analytics_detail" using btree ("jobid");
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX IF EXISTS "public"."media_analytics_detail_media_analytics";
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE INDEX "media_analytics_detail_media_analytics" on
|
||||
"public"."media_analytics_detail" using btree ("media_analytics_id");
|
||||
@@ -0,0 +1,4 @@
|
||||
-- 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;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."media_analytics" add column "unique_documents" numeric
|
||||
null;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- 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;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."media_analytics" add column "duplicate_documents" numeric
|
||||
null;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- 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;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."media_analytics_detail" add column "unique_documents" numeric
|
||||
null;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- 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;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."media_analytics_detail" add column "duplicate_documents" numeric
|
||||
null;
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."media_analytics_detail" rename column "unique_document_count" to "unique_documents";
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."media_analytics_detail" rename column "unique_documents" to "unique_document_count";
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."media_analytics_detail" rename column "duplicate_count" to "duplicate_documents";
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."media_analytics_detail" rename column "duplicate_documents" to "duplicate_count";
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."jobs" add column "dms_id" text
|
||||
-- null;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."jobs" add column "dms_id" text
|
||||
null;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."owners" add column "ownr_ph1_ty" text
|
||||
-- null;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."owners" add column "ownr_ph1_ty" text
|
||||
null;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."owners" add column "ownr_ph2_ty" text
|
||||
-- null;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."owners" add column "ownr_ph2_ty" text
|
||||
null;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."jobs" add column "ownr_ph1_ty" text
|
||||
-- null;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."jobs" add column "ownr_ph1_ty" text
|
||||
null;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."jobs" add column "ownr_ph2_ty" text
|
||||
-- null;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."jobs" add column "ownr_ph2_ty" text
|
||||
null;
|
||||
7
package-lock.json
generated
7
package-lock.json
generated
@@ -23,7 +23,6 @@
|
||||
"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",
|
||||
@@ -3587,12 +3586,6 @@
|
||||
"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,7 +32,6 @@
|
||||
"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,9 +324,6 @@ const applySocketIO = async ({ server, app }) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Legacy Socket Events
|
||||
SetLegacyWebsocketHandlers(io)
|
||||
|
||||
const api = {
|
||||
pubClient,
|
||||
io,
|
||||
@@ -390,6 +387,8 @@ 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 WsLogger = require("../../web-sockets/createLogEvent");
|
||||
const CdkBase = require("../../web-sockets/web-socket");
|
||||
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);
|
||||
|
||||
WsLogger.createJsonEvent(socket, "SILLY", `Raw Request: ${printable}`, x.data);
|
||||
CdkBase.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);
|
||||
WsLogger.createJsonEvent(socket, "SILLY", `Raw Response: ${printable}`, x.data);
|
||||
CdkBase.createJsonEvent(socket, "SILLY", `Raw Response: ${printable}`, x.data);
|
||||
|
||||
return x;
|
||||
});
|
||||
|
||||
async function PbsCalculateAllocationsAp(socket, billids) {
|
||||
try {
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `Received request to calculate allocations for ${billids}`);
|
||||
CdkBase.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) {
|
||||
WsLogger.createLogEvent(
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"ERROR",
|
||||
`No bills found for export. Ensure they have not already been exported and try again.`
|
||||
@@ -147,9 +147,7 @@ async function PbsCalculateAllocationsAp(socket, billids) {
|
||||
...billHash[key],
|
||||
Amount: billHash[key].Amount.toFormat("0.00")
|
||||
});
|
||||
//Calculate the total expense for the bill iteratively to
|
||||
// create the corresponding credit to AP.
|
||||
APAmount = APAmount.add(billHash[key].Amount);
|
||||
APAmount = APAmount.add(billHash[key].Amount); //Calculate the total expense for the bill iteratively to create the corresponding credit to AP.
|
||||
}
|
||||
});
|
||||
|
||||
@@ -168,21 +166,19 @@ async function PbsCalculateAllocationsAp(socket, billids) {
|
||||
|
||||
return transactionlist;
|
||||
} catch (error) {
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error encountered in PbsCalculateAllocationsAp. ${error}`);
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsCalculateAllocationsAp. ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
exports.PbsCalculateAllocationsAp = PbsCalculateAllocationsAp;
|
||||
|
||||
async function QueryBillData(socket, billids) {
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `Querying bill data for id(s) ${billids}`);
|
||||
CdkBase.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 });
|
||||
|
||||
WsLogger.createLogEvent(socket, "SILLY", `Bill data query result ${JSON.stringify(result, null, 2)}`);
|
||||
CdkBase.createLogEvent(socket, "SILLY", `Bill data query result ${JSON.stringify(result, null, 2)}`);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -196,10 +192,40 @@ function getCostAccount(billline, respcenters) {
|
||||
return respcenters.costs.find((c) => c.name === acctName);
|
||||
}
|
||||
|
||||
async function MarkApExported(socket, billids) {
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `Marking bills as exported for id ${billids}`);
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
|
||||
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}`);
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
|
||||
const result = await client
|
||||
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
|
||||
.request(queries.MARK_BILLS_EXPORTED, {
|
||||
@@ -218,36 +244,3 @@ 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,12 +2,10 @@ 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;
|
||||
@@ -21,12 +19,11 @@ 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);
|
||||
|
||||
WsLogger.createJsonEvent(socket, "DEBUG", `Raw Request: ${printable}`, x.data);
|
||||
CdkBase.createJsonEvent(socket, "DEBUG", `Raw Request: ${printable}`, x.data);
|
||||
|
||||
return x;
|
||||
});
|
||||
@@ -36,11 +33,13 @@ axios.interceptors.response.use((x) => {
|
||||
|
||||
const printable = `${new Date()} | Response: ${x.status} ${x.statusText} |${JSON.stringify(x.data)}`;
|
||||
//logRequestToFile(printable);
|
||||
WsLogger.createJsonEvent(socket, "DEBUG", `Raw Response: ${printable}`, x.data);
|
||||
CdkBase.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");
|
||||
@@ -54,20 +53,21 @@ function logRequestToFile(printable) {
|
||||
}
|
||||
}
|
||||
|
||||
const defaultHandler = async (socket, { txEnvelope, jobid }) => {
|
||||
|
||||
exports.default = async function (socket, { txEnvelope, jobid }) {
|
||||
socket.logEvents = [];
|
||||
socket.recordid = jobid;
|
||||
socket.txEnvelope = txEnvelope;
|
||||
try {
|
||||
WsLogger.createLogEvent(socket, "INFO", `Received Job export request for id ${jobid}`);
|
||||
CdkBase.createLogEvent(socket, "INFO", `Received Job export request for id ${jobid}`);
|
||||
|
||||
const JobData = await QueryJobData(socket, jobid);
|
||||
socket.JobData = JobData;
|
||||
WsLogger.createLogEvent(socket, "INFO", `Querying the DMS for the Vehicle Record.`);
|
||||
CdkBase.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?.CustomerRef) {
|
||||
if (socket.DmsVeh && socket.DmsVeh.CustomerRef) {
|
||||
//Get the associated customer from the Vehicle Record.
|
||||
socket.DMSVehCustomer = await QueryCustomerBycodeFromDms(socket, socket.DmsVeh.CustomerRef);
|
||||
}
|
||||
@@ -78,39 +78,35 @@ const defaultHandler = async (socket, { txEnvelope, jobid }) => {
|
||||
...socket.DMSCustList
|
||||
]);
|
||||
} catch (error) {
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error encountered in PbsJobExport. ${error}`);
|
||||
CdkBase.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) {
|
||||
WsLogger.createLogEvent(socket, "INFO", `User selected customer ${selectedCustomerId || "NEW"}`);
|
||||
CdkBase.createLogEvent(socket, "INFO", `User selected customer ${selectedCustomerId || "NEW"}`);
|
||||
|
||||
//Upsert the contact information as per Wafaa's Email.
|
||||
WsLogger.createLogEvent(
|
||||
CdkBase.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;
|
||||
|
||||
WsLogger.createLogEvent(socket, "INFO", `Upserting vehicle information to DMS for ${socket.JobData.v_vin}`);
|
||||
CdkBase.createLogEvent(socket, "INFO", `Upserting vehicle information to DMS for ${socket.JobData.v_vin}`);
|
||||
const vehicleRef = await UpsertVehicleData(socket, ownerRef.ReferenceId);
|
||||
socket.vehicleRef = vehicleRef;
|
||||
} else {
|
||||
WsLogger.createLogEvent(
|
||||
CdkBase.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;
|
||||
@@ -118,21 +114,22 @@ exports.PbsSelectedCustomer = async function PbsSelectedCustomer(socket, selecte
|
||||
const vehicleRef = await GetVehicleData(socket, socket.ownerRef?.ReferenceId || socket.selectedCustomerId);
|
||||
socket.vehicleRef = vehicleRef;
|
||||
}
|
||||
WsLogger.createLogEvent(socket, "INFO", `Inserting account posting data...`);
|
||||
CdkBase.createLogEvent(socket, "INFO", `Inserting accounting 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)
|
||||
}
|
||||
WsLogger.createLogEvent(socket, "INFO", `Marking job as exported.`);
|
||||
CdkBase.createLogEvent(socket, "INFO", `Marking job as exported.`);
|
||||
await MarkJobExported(socket, socket.JobData.id);
|
||||
socket.emit("export-success", socket.JobData.id);
|
||||
} else {
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Export was not successful.`);
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Export was not successful.`);
|
||||
}
|
||||
} catch (error) {
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error encountered in PbsSelectedCustomer. ${error}`);
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsSelectedCustomer. ${error}`);
|
||||
await InsertFailedExportLog(socket, error);
|
||||
}
|
||||
};
|
||||
@@ -140,24 +137,22 @@ exports.PbsSelectedCustomer = async function PbsSelectedCustomer(socket, selecte
|
||||
// Was Successful
|
||||
async function CheckForErrors(socket, response) {
|
||||
if (response.WasSuccessful === undefined || response.WasSuccessful === true) {
|
||||
WsLogger.createLogEvent(socket, "INFO", `Successful response from DMS. ${response.Message || ""}`);
|
||||
CdkBase.createLogEvent(socket, "INFO", `Successful response from DMS. ${response.Message || ""}`);
|
||||
} else {
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error received from DMS: ${response.Message}`);
|
||||
WsLogger.createLogEvent(socket, "DEBUG", `Error received from DMS: ${JSON.stringify(response)}`);
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error received from DMS: ${response.Message}`);
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `Error received from DMS: ${JSON.stringify(response)}`);
|
||||
}
|
||||
}
|
||||
|
||||
exports.CheckForErrors = CheckForErrors;
|
||||
|
||||
async function QueryJobData(socket, jobid) {
|
||||
WsLogger.createLogEvent(socket, "INFO", `Querying job data for id ${jobid}`);
|
||||
CdkBase.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 });
|
||||
|
||||
//WsLogger.createLogEvent(socket, "DEBUG", `Job data query result ${JSON.stringify(result, null, 2)}`);
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `Job data query result ${JSON.stringify(result, null, 2)}`);
|
||||
return result.jobs_by_pk;
|
||||
}
|
||||
|
||||
@@ -196,7 +191,7 @@ async function QueryVehicleFromDms(socket) {
|
||||
CheckForErrors(socket, VehicleGetResponse);
|
||||
return VehicleGetResponse;
|
||||
} catch (error) {
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in QueryVehicleFromDms - ${error}`);
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in QueryVehicleFromDms - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -225,9 +220,9 @@ async function QueryCustomersFromDms(socket) {
|
||||
{ auth: PBS_CREDENTIALS, socket }
|
||||
);
|
||||
CheckForErrors(socket, CustomerGetResponse);
|
||||
return CustomerGetResponse?.Contacts;
|
||||
return CustomerGetResponse && CustomerGetResponse.Contacts;
|
||||
} catch (error) {
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in QueryCustomersFromDms - ${error}`);
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in QueryCustomersFromDms - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -258,9 +253,9 @@ async function QueryCustomerBycodeFromDms(socket, CustomerRef) {
|
||||
{ auth: PBS_CREDENTIALS, socket }
|
||||
);
|
||||
CheckForErrors(socket, CustomerGetResponse);
|
||||
return CustomerGetResponse?.Contacts;
|
||||
return CustomerGetResponse && CustomerGetResponse.Contacts;
|
||||
} catch (error) {
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in QueryCustomersFromDms - ${error}`);
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in QueryCustomersFromDms - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -277,15 +272,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",
|
||||
@@ -342,7 +337,7 @@ async function UpsertContactData(socket, selectedCustomerId) {
|
||||
CheckForErrors(socket, ContactChangeResponse);
|
||||
return ContactChangeResponse;
|
||||
} catch (error) {
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in UpsertContactData - ${error}`);
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in UpsertContactData - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -362,10 +357,10 @@ async function UpsertVehicleData(socket, ownerRef) {
|
||||
//FleetNumber: "String",
|
||||
//Status: "String",
|
||||
OwnerRef: ownerRef, // "00000000000000000000000000000000",
|
||||
// ModelNumber: socket.JobData.vehicle?.v_makecode,
|
||||
// ModelNumber: socket.JobData.vehicle && socket.JobData.vehicle.v_makecode,
|
||||
Make: socket.JobData.v_make_desc,
|
||||
Model: socket.JobData.v_model_desc,
|
||||
Trim: socket.JobData.vehicle?.v_trimcode,
|
||||
Trim: socket.JobData.vehicle && socket.JobData.vehicle.v_trimcode,
|
||||
//VehicleType: "String",
|
||||
Year: socket.JobData.v_model_yr,
|
||||
Odometer: socket.JobData.kmout,
|
||||
@@ -495,16 +490,14 @@ async function UpsertVehicleData(socket, ownerRef) {
|
||||
CheckForErrors(socket, VehicleChangeResponse);
|
||||
return VehicleChangeResponse;
|
||||
} catch (error) {
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in UpsertVehicleData - ${error}`);
|
||||
CdkBase.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,
|
||||
@@ -517,7 +510,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",
|
||||
@@ -535,21 +528,24 @@ 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 {
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in Getting Vehicle Data - ${Vehicles.length} vehicle(s) found`);
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in Getting Vehicle Data - ${Vehicles.length} vehicle(s) found`);
|
||||
}
|
||||
} catch (error) {
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in UpsertVehicleData - ${error}`);
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in UpsertVehicleData - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function InsertAccountPostingData(socket) {
|
||||
try {
|
||||
const allocations = await CalculateAllocations(socket, socket.JobData.id);
|
||||
@@ -633,8 +629,7 @@ async function InsertAccountPostingData(socket) {
|
||||
Posting: {
|
||||
Reference: socket.JobData.ro_number,
|
||||
JournalCode: socket.txEnvelope.journal,
|
||||
//Sample TransactionDate: "0001-01-01T00:00:00.0000000Z",
|
||||
TransactionDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString(),
|
||||
TransactionDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString(), //"0001-01-01T00:00:00.0000000Z",
|
||||
Description: socket.txEnvelope.story,
|
||||
//AdditionalInfo: "String",
|
||||
Source: InstanceManager({ imex: "ImEX Online", rome: "Rome Online" }),
|
||||
@@ -647,15 +642,14 @@ async function InsertAccountPostingData(socket) {
|
||||
CheckForErrors(socket, AccountPostingChange);
|
||||
return AccountPostingChange;
|
||||
} catch (error) {
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in InsertAccountPostingData - ${error}`);
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in InsertAccountPostingData - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function MarkJobExported(socket, jobid) {
|
||||
WsLogger.createLogEvent(socket, "INFO", `Marking job as exported for id ${jobid}`);
|
||||
CdkBase.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, {
|
||||
@@ -683,52 +677,47 @@ 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, {
|
||||
logs: [{
|
||||
log: {
|
||||
bodyshopid: socket.JobData.bodyshop.id,
|
||||
jobid: socket.JobData.id,
|
||||
successful: false,
|
||||
message: JSON.stringify(error),
|
||||
useremail: socket.user.email
|
||||
}]
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (error2) {
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in InsertFailedExportLog - ${error} - ${JSON.stringify(error2)}`);
|
||||
CdkBase.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;
|
||||
WsLogger.createLogEvent(socket, "INFO", `No repair orders found for vehicle. Inserting record.`);
|
||||
CdkBase.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) {
|
||||
WsLogger.createLogEvent(socket, "INFO", `ROs found for vehicle, but none match. Inserting record.`);
|
||||
const InsertedRepairOrder = await RepairOrderChange(socket);
|
||||
CdkBase.createLogEvent(socket, "INFO", `ROs found for vehicle, but none match. Inserting record.`);
|
||||
const InsertedRepairOrder = await RepairOrderChange(socket)
|
||||
socket.InsertedRepairOrder = InsertedRepairOrder;
|
||||
} else {
|
||||
WsLogger.createLogEvent(
|
||||
socket,
|
||||
"WARN",
|
||||
`Repair order appears to already exist in PBS. ${matchingRo.RepairOrderNumber}`
|
||||
);
|
||||
CdkBase.createLogEvent(socket, "WARN", `Repair order appears to already exist in PBS. ${matchingRo.RepairOrderNumber}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in CreateRepairOrderInPBS - ${error} - ${JSON.stringify(error)}`);
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in CreateRepairOrderInPBS - ${error} - ${JSON.stringify(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -744,7 +733,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",
|
||||
@@ -760,7 +749,7 @@ async function RepairOrderGet(socket) {
|
||||
CheckForErrors(socket, RepairOrderGet);
|
||||
return RepairOrderGet;
|
||||
} catch (error) {
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in RepairOrderChange - ${error}`);
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in RepairOrderChange - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -769,32 +758,31 @@ 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: [
|
||||
{
|
||||
@@ -803,59 +791,61 @@ 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) {
|
||||
WsLogger.createLogEvent(socket, "ERROR", `Error in RepairOrderChange - ${error}`);
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in RepairOrderChange - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user