Compare commits

..

79 Commits

Author SHA1 Message Date
Allan Carr
646754732d IO-2782 Adjust to Object for items
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-09-20 16:45:48 -07:00
Dave Richer
efc1157653 IO-2782 - Fix query
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-20 18:53:33 -04:00
Dave Richer
a5d3f2caf1 IO-2782-Send-Promanager-Welcome-Email - Update for merge conflict
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-19 13:11:02 -04:00
Dave Richer
4ad87a522c IO-2782-Send-Promanager-Welcome-Email - Update for merge conflict
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-19 13:08:23 -04:00
Dave Richer
145cf7cc93 IO-2782-Send-Promanager-Welcome-Email - Finalize cleanup
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-19 12:54:26 -04:00
Dave Richer
cdb2d4d2d6 IO-2782-Send-Promanager-Welcome-Email - Cleanup of adminRoutes / firebase-handler.js
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-19 11:29:13 -04:00
Dave Richer
29f0031c1e IO-2782-Send-Promanager-Welcome-Email - Send ProManager welcome email
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-18 18:30:02 -04:00
Patrick Fic
e3059b41ae IO-2933 Resolve PR comments. 2024-09-18 11:19:43 -07:00
Patrick Fic
2a33f462a3 IO-2933 Add in email for succesful postback from Short URL. 2024-09-16 16:02:19 -07:00
Patrick Fic
cbc164dbeb IO-2933 Add in ability to text payments for multiple ROs. 2024-09-16 14:33:17 -07:00
Patrick Fic
6382fdf19c Merge branch 'feature/IO-2920-cash-discounting' into release/2024-09-20 2024-09-16 12:23:53 -07:00
Patrick Fic
9287e6608d IO-2920 Pretty JSON Translation 2024-09-16 12:23:29 -07:00
Patrick Fic
d221763064 Merge branch 'feature/IO-2920-cash-discounting' into release/2024-09-20 2024-09-16 12:13:13 -07:00
Patrick Fic
b39a5b755e IO-2920 Add hasura changes for cash discount & add config page. 2024-09-16 12:07:35 -07:00
Dave Richer
449330441a Merged in release/2024-09-13 (pull request #1725)
Release/2024 09 13 - IO-2913 - IO-2915 - IO-2733 - IO-2923 - IO-2925 - IO-2928 - IO-2927 - IO-2928 - IO-2913 - IO-2733 - IO-2926
2024-09-16 16:28:40 +00:00
Dave Richer
fcab5e6ef2 Merged in feature/IO-2926-Vendor-Discount-Wrapping-In-Parts-Order (pull request #1723)
feature/IO-2926-Vendor-Discount-Wrapping-In-Parts-Order - Fix Wrapping in Vendor Search Select
2024-09-16 16:18:05 +00:00
Dave Richer
0212b837ea feature/IO-2926-Vendor-Discount-Wrapping-In-Parts-Order - Fix Wrapping in Vendor Search Select
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-16 12:16:29 -04:00
Patrick Fic
e7438a099e Merged in feature/IO-2733-pwa-timer (pull request #1721)
IO-2733 Add loading state and further delay reload.
2024-09-13 18:24:11 +00:00
Patrick Fic
b3303e3c38 IO-2733 Add loading state and further delay reload. 2024-09-13 11:22:38 -07:00
Patrick Fic
c69c86d193 Merged in feature/IO-2733-pwa-timer (pull request #1719)
IO-2733 Resolve notification showing incorrect time.
2024-09-13 17:51:33 +00:00
Patrick Fic
73ec8b8a70 IO-2733 Resolve notification showing incorrect time. 2024-09-13 10:51:04 -07:00
Patrick Fic
af09796df8 Merged in feature/IO-2733-pwa-timer (pull request #1717)
IO-2733 Add Timer Started check to prevent auto refresh early.
2024-09-13 16:59:58 +00:00
Patrick Fic
954504de8d IO-2733 Add Timer Started check to prevent auto refresh early. 2024-09-13 09:58:46 -07:00
Allan Carr
0aba040338 Merged in feature/IO-2928-QBO-CAUSA-Payable-TAX (pull request #1714)
IO-2928 QBO CA US Tax  Accumulator

Approved-by: Patrick Fic
2024-09-13 14:43:58 +00:00
Allan Carr
c3bfe87674 Merge branch 'feature/IO-2913-ADP-Payroll' into release/2024-09-13
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>

# Conflicts:
#	client/src/translations/en_us/common.json
#	client/src/translations/es/common.json
#	client/src/translations/fr/common.json
2024-09-12 19:59:47 -07:00
Allan Carr
9aa1279144 IO-2913 Add in Translations
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-09-12 19:53:50 -07:00
Allan Carr
4e6c45b195 IO-2928 Null coalesce billline amount
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-09-12 17:13:29 -07:00
Allan Carr
4fdb939bd2 Merged in feature/IO-2927-qbo-usa-gst-itc (pull request #1715)
IO-2927 Extend Logging passthru

Approved-by: Patrick Fic
2024-09-12 23:58:24 +00:00
Allan Carr
062a1dcc72 IO-2927 Extend Logging passthru
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-09-12 16:29:37 -07:00
Allan Carr
7b420b1855 IO-2928 QBO CA US Tax Accumulator
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-09-12 16:27:43 -07:00
Allan Carr
40f61bbc8f Merged in feature/IO-2927-qbo-usa-gst-itc (pull request #1713)
IO-2927 Correct accountmeta
2024-09-12 22:23:54 +00:00
Allan Carr
f5d821c394 Merged in feature/IO-2927-qbo-usa-gst-itc (pull request #1712)
IO-2927 Correct accountmeta
2024-09-12 22:12:32 +00:00
Allan Carr
3958ec9189 IO-2927 Correct accountmeta
accounts doesn't exist in recievables, switch to items

Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-09-12 15:11:06 -07:00
Allan Carr
1e4f52e541 Merged in feature/IO-2927-qbo-usa-gst-itc (pull request #1710)
Feature/IO-2927 qbo usa gst itc

IO-2927
2024-09-12 20:33:27 +00:00
Patrick Fic
5cc5cb444e Merged in feature/IO-2927-qbo-usa-gst-itc (pull request #1709)
IO-2997 Add better error handling for 400 requests.

Approved-by: Allan Carr
2024-09-12 20:26:16 +00:00
Patrick Fic
4acf0c59ca IO-2997 Remove unnecessary comment. 2024-09-12 13:25:14 -07:00
Patrick Fic
2858a5e871 IO-2997 Add better error handling for 400 requests. 2024-09-12 13:23:53 -07:00
Patrick Fic
24496d3ee1 Merged in feature/IO-2927-qbo-usa-gst-itc (pull request #1707)
IO-2927 Update QBO Payable to use ITC.

Approved-by: Allan Carr
2024-09-12 20:04:33 +00:00
Patrick Fic
0a5df69b12 IO-2927 Update QBO Payable to use ITC. 2024-09-12 13:03:23 -07:00
Patrick Fic
80efea02c6 Merged in feature/IO-2925-ppc-40-points (pull request #1704)
IO-2925 Add 40% as PPC choice.

Approved-by: Allan Carr
2024-09-12 17:15:03 +00:00
Patrick Fic
9f5c282b41 IO-2925 Add 40% as PPC choice. 2024-09-12 09:19:49 -07:00
Allan Carr
b2602c3385 Merged in feature/IO-2923-Edit-Bill-Line-original_actual_price (pull request #1702)
IO-2923 Edit Bill Line original_actual_price

Approved-by: Dave Richer
2024-09-12 15:54:58 +00:00
Allan Carr
0e584af424 IO-2923 Edit Bill Line original_actual_price
IO-2923 Edit Bill Line original_actual_price

Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-09-11 16:36:28 -07:00
Patrick Fic
cdc3de2a33 Merge branch 'feature/IO-2733-pwa-timer' into release/2024-09-13 2024-09-10 15:58:59 -07:00
Patrick Fic
3bfa556b02 IO-2733 Added countdown timer to PWA Refresh & cache busting meta. 2024-09-10 15:54:15 -07:00
Allan Carr
44cb7577e2 Merged in feature/IO-2913-ADP-Payroll (pull request #1698)
IO-2913 ADP Payroll Reports

Approved-by: Dave Richer
2024-09-10 20:24:51 +00:00
Allan Carr
46d2b08477 Merged in feature/IO-2915-Customer-Portion-Totals-Federal-Tax (pull request #1699)
IO-2915 Customer Portion Totals - Federal Tax

Approved-by: Dave Richer
2024-09-10 20:24:13 +00:00
Allan Carr
0193ff9e65 IO-2915 Customer Portion Totals - Federal Tax
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-09-10 11:55:42 -07:00
Allan Carr
fd9a51209f IO-2913 ADP Payroll Reports
Uses ADPPayroll Split

Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-09-10 11:21:03 -07:00
Dave Richer
d0a7b87e04 Merged in feature/IO-2916-Remove-Beta-Switch-AIO (pull request #1693)
feature/IO-2916-Remove-Beta-Switch-AIO - Remove Beta Switch
2024-09-10 17:29:31 +00:00
Dave Richer
799b24c90e feature/IO-2916-Remove-Beta-Switch-LEGACY - Remove cookie
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-10 13:16:08 -04:00
Dave Richer
3e1a8c87d1 feature/IO-2916-Remove-Beta-Switch-AIO - Remove cookie
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-10 13:10:49 -04:00
Dave Richer
c886d874de feature/IO-2916-Remove-Beta-Switch-AIO - Remove Beta Switch
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-09 23:34:54 -04:00
Allan Carr
4dfb020089 Merged in release/2024-09-06 (pull request #1691)
Release/2024 09 06
2024-09-07 01:08:25 +00:00
Patrick Fic
bc6f05acbc IO-2907 change CI step to deploy instead of build 2024-09-06 13:55:53 -07:00
Patrick Fic
2701bbd501 IO-2907 Resolve Hasura on CI and improve Jira notify. 2024-09-06 13:43:58 -07:00
Patrick Fic
1f2040d97c IO-2907 Updated CI to update Jira #1. 2024-09-06 13:32:48 -07:00
Allan Carr
43963a3e91 Merged in feature/IO-2904-Production-Board-Visual-Subtotal (pull request #1687)
IO-2904 Production Board Visual Subtotal

Approved-by: Dave Richer
2024-09-06 18:10:49 +00:00
Allan Carr
4287311adb Merged in feature/IO-2902-Duplicate-RBAC-Items (pull request #1686)
IO-2902 Duplicate RBAC Items

Approved-by: Dave Richer
2024-09-06 18:09:15 +00:00
Allan Carr
d0e8589a76 Merged in feature/IO-2893-Editing-Shift-Tickets (pull request #1685)
IO-2893 Enhance disable of editing of tickets

Approved-by: Dave Richer
2024-09-06 18:08:26 +00:00
Allan Carr
c4bab72947 DB change to .env
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-09-05 16:36:59 -07:00
Allan Carr
aa4b4998fa IO-2904 Production Board Visual Subtotal
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-09-05 16:18:49 -07:00
Patrick Fic
ed4566e00f Merge branch 'feature/IO-2707-timeticket-rerender' into release/2024-09-06 2024-09-05 13:51:42 -07:00
Patrick Fic
5c2cdfe16c Merge branch 'feature/IO-2724-tech-modal-duplicated' into release/2024-09-06 2024-09-05 13:51:34 -07:00
Patrick Fic
12c75357b5 Revert IP tracking to only single device users. 2024-09-05 11:53:48 -07:00
Patrick Fic
d40f3ee45a Add in IP tracking for SingleDeviceOnly checks. 2024-09-05 11:52:06 -07:00
Allan Carr
96a0def846 IO-2902 Fix prettier formatting
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-09-05 11:44:26 -07:00
Allan Carr
1fd595d0de IO-2902 Duplicate RBAC Items
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-09-05 11:42:11 -07:00
Allan Carr
52cf4f3d1f IO-2893 Enhance disable of editing of tickets
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-09-05 11:27:51 -07:00
Dave Richer
4d9be1d232 Merged in bugfix/dinero-for-production-board-amounts (pull request #1684)
- removed unused function
2024-09-05 17:59:41 +00:00
Dave Richer
fb2bc20b4f - removed unused function
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-05 13:59:03 -04:00
Dave Richer
744593e96a Merged in bugfix/dinero-for-production-board-amounts (pull request #1682)
Bugfix/dinero for production board amounts
2024-09-05 17:40:27 +00:00
Dave Richer
1e9308be9b Merged in bugfix/dinero-for-production-board-amounts (pull request #1681)
- Use Dinero in place of straight math in production board
2024-09-05 17:40:00 +00:00
Dave Richer
411605e121 - Use Dinero in place of straight math in production board
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-05 13:38:50 -04:00
Dave Richer
1da8d6abb3 Merged in bugfix/dinero-for-production-board-amounts (pull request #1680)
- Use Dinero in place of straight math in production board
2024-09-05 16:48:21 +00:00
Dave Richer
cdcef798df - Use Dinero in place of straight math in production board
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-05 12:47:19 -04:00
Patrick Fic
f7207a9f3f Add missing PWA dependency for Vite. 2024-09-05 09:17:42 -07:00
Patrick Fic
7a54b55bd4 IO-2724 Resolve tech console showing 2 drawers on production board. 2024-09-05 08:26:49 -07:00
Patrick Fic
991dfc2ad5 IO-2707 resolve time ticket modal rerender issue. 2024-09-05 08:17:45 -07:00
53 changed files with 8113 additions and 5091 deletions

View File

@@ -5,6 +5,7 @@ orbs:
aws-s3: circleci/aws-s3@4.0.0
aws-cli: circleci/aws-cli@4.0
eb: circleci/aws-elastic-beanstalk@2.0.1
jira: circleci/jira@2.1.0
jobs:
imex-api-deploy:
docker:
@@ -18,6 +19,12 @@ jobs:
eb status --verbose
eb deploy
eb status
- jira/notify:
environment: Production (ImEX) - API
environment_type: production
job_type: deployment
pipeline_id: << pipeline.id >>
pipeline_number: << pipeline.number >>
imex-hasura-migrate:
docker:
@@ -33,11 +40,16 @@ jobs:
- run:
name: Execute migration
command: |
npm install hasura-cli -g
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
hasura migrate apply --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
hasura metadata apply --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
hasura metadata reload --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
- jira/notify:
environment: Production (ImEX) - Hasura
environment_type: production
pipeline_id: << pipeline.id >>
job_type: deployment
pipeline_number: << pipeline.number >>
imex-app-build:
docker:
- image: cimg/node:18.18.2
@@ -62,6 +74,7 @@ jobs:
to: "s3://imex-online-production/"
arguments: "--exclude '*.map'"
imex-app-beta-build:
docker:
- image: cimg/node:18.18.2
@@ -86,6 +99,12 @@ jobs:
from: dist
to: "s3://imex-online-beta/"
arguments: "--exclude '*.map'"
- jira/notify:
environment: Production (ImEX) - Front End
environment_type: production
pipeline_id: << pipeline.id >>
job_type: deployment
pipeline_number: << pipeline.number >>
rome-api-deploy:
docker:
@@ -99,7 +118,12 @@ jobs:
eb status --verbose
eb deploy
eb status
- jira/notify:
environment: Production (Rome) - API
environment_type: production
pipeline_id: << pipeline.id >>
job_type: deployment
pipeline_number: << pipeline.number >>
rome-hasura-migrate:
docker:
- image: cimg/node:18.18.2
@@ -114,11 +138,16 @@ jobs:
- run:
name: Execute migration
command: |
npm install hasura-cli -g
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
hasura migrate apply --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
hasura metadata apply --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
hasura metadata reload --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
- jira/notify:
environment: Production (Rome) - Hasura
environment_type: production
pipeline_id: << pipeline.id >>
job_type: deployment
pipeline_number: << pipeline.number >>
rome-app-build:
docker:
- image: cimg/node:18.18.2
@@ -143,6 +172,12 @@ jobs:
from: dist
to: "s3://rome-online-production/"
arguments: "--exclude '*.map'"
- jira/notify:
environment: Production (Rome) - Front End
environment_type: production
pipeline_id: << pipeline.id >>
job_type: deployment
pipeline_number: << pipeline.number >>
promanager-app-build:
docker:
@@ -168,6 +203,12 @@ jobs:
from: dist
to: "s3://promanager-production/"
arguments: "--exclude '*.map'"
- jira/notify:
environment: Production (ProManager) - Front End
environment_type: production
pipeline_id: << pipeline.id >>
job_type: deployment
pipeline_number: << pipeline.number >>
test-rome-hasura-migrate:
docker:
@@ -183,10 +224,18 @@ jobs:
- run:
name: Execute migration
command: |
npm install hasura-cli -g
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
hasura migrate apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
sleep 5
hasura metadata apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
sleep 10
hasura metadata reload --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
- jira/notify:
environment: Test (Rome) - Hasura
environment_type: testing
pipeline_id: << pipeline.id >>
job_type: deployment
pipeline_number: << pipeline.number >>
test-rome-app-build:
docker:
@@ -212,6 +261,12 @@ jobs:
from: dist
to: "s3://rome-online-test/"
arguments: "--exclude '*.map'"
- jira/notify:
environment: Test (Rome) - Front End
environment_type: testing
pipeline_id: << pipeline.id >>
job_type: deployment
pipeline_number: << pipeline.number >>
test-promanager-app-build:
docker:
@@ -237,6 +292,12 @@ jobs:
from: dist
to: "s3://promanager-testing/"
arguments: "--exclude '*.map'"
- jira/notify:
environment: Test (ProManager) - Front End
environment_type: testing
pipeline_id: << pipeline.id >>
job_type: deployment
pipeline_number: << pipeline.number >>
test-hasura-migrate:
docker:
@@ -252,10 +313,18 @@ jobs:
- run:
name: Execute migration
command: |
npm install hasura-cli -g
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
hasura migrate apply --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
sleep 15
hasura metadata apply --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
sleep 30
hasura metadata reload --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
- jira/notify:
environment: Test (ImEX) - Hasura
environment_type: testing
pipeline_id: << pipeline.id >>
job_type: deployment
pipeline_number: << pipeline.number >>
imex-test-app-build:
docker:
@@ -302,7 +371,12 @@ jobs:
from: dist
to: "s3://imex-online-test-beta/"
arguments: "--exclude '*.map'"
- jira/notify:
environment: Test (ImEX) - Front End
environment_type: testing
pipeline_id: << pipeline.id >>
job_type: deployment
pipeline_number: << pipeline.number >>
admin-app-build:
docker:
@@ -353,7 +427,7 @@ workflows:
secret: ${HASURA_PROD_SECRET}
filters:
branches:
only: master
only: master-AIO
- rome-api-deploy:
filters:
branches:
@@ -363,7 +437,7 @@ workflows:
branches:
only: master-AIO
- rome-hasura-migrate:
secret: ${HASURA_PROD_SECRET}
secret: ${HASURA_ROME_PROD_SECRET}
filters:
branches:
only: master-AIO

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql
VITE_APP_GA_CODE=231099835
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test

View File

@@ -1,5 +1,5 @@
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql
VITE_APP_GA_CODE=231099835
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test

View File

@@ -1,7 +1,8 @@
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql
VITE_APP_GA_CODE=231099835
VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
# VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
VITE_APP_CLOUDINARY_API_KEY=957865933348715

View File

@@ -2,6 +2,9 @@
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
<link rel="icon" href="/favicon.png"/>
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>

894
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,35 +9,35 @@
"proxy": "http://localhost:4000",
"dependencies": {
"@ant-design/pro-layout": "^7.19.12",
"@apollo/client": "^3.11.6",
"@apollo/client": "^3.11.4",
"@emotion/is-prop-valid": "^1.3.0",
"@fingerprintjs/fingerprintjs": "^4.4.3",
"@jsreport/browser-client": "^3.1.0",
"@reduxjs/toolkit": "^2.2.7",
"@sentry/cli": "^2.34.1",
"@sentry/cli": "^2.33.1",
"@sentry/react": "^7.114.0",
"@splitsoftware/splitio-react": "^1.12.1",
"@tanem/react-nprogress": "^5.0.51",
"@vitejs/plugin-react": "^4.3.1",
"antd": "^5.20.5",
"antd": "^5.20.1",
"apollo-link-logger": "^2.0.1",
"apollo-link-sentry": "^3.3.0",
"autosize": "^6.0.1",
"axios": "^1.7.7",
"axios": "^1.7.4",
"classnames": "^2.5.1",
"css-box-model": "^1.2.1",
"dayjs": "^1.11.13",
"dayjs": "^1.11.12",
"dayjs-business-days2": "^1.2.2",
"dinero.js": "^1.9.1",
"dotenv": "^16.4.5",
"env-cmd": "^10.1.0",
"exifr": "^7.1.3",
"firebase": "^10.13.1",
"firebase": "^10.12.5",
"graphql": "^16.9.0",
"i18next": "^23.14.0",
"i18next": "^23.12.3",
"i18next-browser-languagedetector": "^8.0.0",
"immutability-helper": "^3.1.1",
"libphonenumber-js": "^1.11.7",
"libphonenumber-js": "^1.11.5",
"logrocket": "^8.1.2",
"markerjs2": "^2.32.1",
"memoize-one": "^6.0.0",
@@ -47,7 +47,7 @@
"query-string": "^9.1.0",
"raf-schd": "^4.0.3",
"react": "^18.3.1",
"react-big-calendar": "^1.13.4",
"react-big-calendar": "^1.13.2",
"react-color": "^2.19.3",
"react-cookie": "^7.2.0",
"react-dom": "^18.3.1",
@@ -58,15 +58,15 @@
"react-icons": "^5.3.0",
"react-image-lightbox": "^5.1.4",
"react-markdown": "^9.0.1",
"react-number-format": "^5.4.1",
"react-number-format": "^5.4.0",
"react-popopo": "^2.1.9",
"react-product-fruits": "^2.2.61",
"react-product-fruits": "^2.2.6",
"react-redux": "^9.1.2",
"react-resizable": "^3.0.5",
"react-router-dom": "^6.26.1",
"react-router-dom": "^6.26.0",
"react-sticky": "^6.0.3",
"react-virtualized": "^9.22.5",
"react-virtuoso": "^4.10.2",
"react-virtuoso": "^4.10.1",
"recharts": "^2.12.7",
"redux": "^5.0.1",
"redux-actions": "^3.0.3",
@@ -74,9 +74,9 @@
"redux-saga": "^1.3.0",
"redux-state-sync": "^3.1.4",
"reselect": "^5.1.1",
"sass": "^1.78.0",
"sass": "^1.77.8",
"socket.io-client": "^4.7.5",
"styled-components": "^6.1.13",
"styled-components": "^6.1.12",
"subscriptions-transport-ws": "^0.11.0",
"use-memo-one": "^1.1.3",
"userpilot": "^1.3.5",
@@ -130,15 +130,15 @@
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-react": "^7.24.7",
"@dotenvx/dotenvx": "^1.13.3",
"@dotenvx/dotenvx": "^1.7.0",
"@emotion/babel-plugin": "^11.12.0",
"@emotion/react": "^11.13.3",
"@sentry/webpack-plugin": "^2.22.4",
"@emotion/react": "^11.13.0",
"@sentry/webpack-plugin": "^2.22.2",
"@testing-library/cypress": "^10.0.2",
"browserslist": "^4.23.3",
"browserslist-to-esbuild": "^2.1.1",
"cross-env": "^7.0.3",
"cypress": "^13.14.1",
"cypress": "^13.13.3",
"eslint": "^8.57.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-cypress": "^2.15.1",
@@ -147,12 +147,13 @@
"react-error-overlay": "6.0.11",
"redux-logger": "^3.0.6",
"source-map-explorer": "^2.5.3",
"vite": "^5.4.3",
"vite": "^5.4.0",
"vite-plugin-babel": "^1.2.0",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-legacy": "^2.1.0",
"vite-plugin-node-polyfills": "^0.22.0",
"vite-plugin-pwa": "^0.20.4",
"vite-plugin-style-import": "^2.0.0"
"vite-plugin-pwa": "^0.20.1",
"vite-plugin-style-import": "^2.0.0",
"workbox-window": "^7.1.0"
}
}

View File

@@ -18,7 +18,6 @@ import { checkUserSession } from "../redux/user/user.actions";
import { selectBodyshop, selectCurrentEula, selectCurrentUser } from "../redux/user/user.selectors";
import PrivateRoute from "../components/PrivateRoute";
import "./App.styles.scss";
import handleBeta from "../utils/handleBeta";
import Eula from "../components/eula/eula.component";
import InstanceRenderMgr from "../utils/instanceRenderMgr";
import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";
@@ -108,8 +107,6 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
return <LoadingSpinner message={t("general.labels.loggingin")} />;
}
handleBeta();
if (!online) {
return (
<Result

View File

@@ -98,7 +98,7 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
});
billlines.forEach((billline) => {
const { deductedfromlbr, inventories, jobline, ...il } = billline;
const { deductedfromlbr, inventories, jobline, original_actual_price, create_ppc, ...il } = billline;
delete il.__typename;
if (il.id) {

View File

@@ -1,6 +1,6 @@
import { DeleteFilled } from "@ant-design/icons";
import { DeleteFilled, CopyFilled } from "@ant-design/icons";
import { useLazyQuery, useMutation } from "@apollo/client";
import { Button, Card, Col, Form, Input, Row, Space, Spin, Statistic, notification } from "antd";
import { Button, Card, Col, Form, Input, Row, Space, Spin, Statistic, message, notification } from "antd";
import axios from "axios";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
@@ -14,10 +14,12 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
import { getCurrentUser } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
cardPaymentModal: selectCardPayment,
bodyshop: selectBodyshop
bodyshop: selectBodyshop,
currentUser: getCurrentUser
});
const mapDispatchToProps = (dispatch) => ({
@@ -25,11 +27,17 @@ const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment"))
});
const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisible, insertAuditTrail }) => {
const CardPaymentModalComponent = ({
bodyshop,
currentUser,
cardPaymentModal,
toggleModalVisible,
insertAuditTrail
}) => {
const { context, actions } = cardPaymentModal;
const [form] = Form.useForm();
const [paymentLink, setPaymentLink] = useState();
const [loading, setLoading] = useState(false);
// const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
@@ -51,8 +59,7 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
//2024-04-25: Nothing is going to happen here anymore. We'll completely rely on the callback.
//Add a slight delay to allow the refetch to properly get the data.
setTimeout(() => {
if (actions && actions.refetch && typeof actions.refetch === "function")
actions.refetch();
if (actions && actions.refetch && typeof actions.refetch === "function") actions.refetch();
setLoading(false);
toggleModalVisible();
}, 750);
@@ -86,7 +93,6 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
});
};
const handleIntelliPayCharge = async () => {
setLoading(true);
//Validate
@@ -101,7 +107,7 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
const response = await axios.post("/intellipay/lightbox_credentials", {
bodyshop,
refresh: !!window.intellipay,
paymentSplitMeta: form.getFieldsValue(),
paymentSplitMeta: form.getFieldsValue()
});
if (window.intellipay) {
@@ -126,6 +132,42 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
}
};
const handleIntelliPayChargeShortLink = async () => {
setLoading(true);
//Validate
try {
await form.validateFields();
} catch (error) {
setLoading(false);
return;
}
try {
const { payments } = form.getFieldsValue();
const response = await axios.post("/intellipay/generate_payment_url", {
bodyshop,
amount: payments?.reduce((acc, val) => {
return acc + (val?.amount || 0);
}, 0),
account: payments && data && data.jobs.length > 0 ? data.jobs.map((j) => j.ro_number).join(", ") : null,
comment: btoa(JSON.stringify({ payments, userEmail: currentUser.email })),
paymentSplitMeta: form.getFieldsValue()
});
if (response.data) {
setPaymentLink(response.data?.shorUrl);
navigator.clipboard.writeText(response.data?.shorUrl);
message.success(t("general.actions.copied"));
}
setLoading(false);
} catch (error) {
notification.open({
type: "error",
message: t("job_payments.notifications.error.openingip")
});
setLoading(false);
}
};
return (
<Card title="Card Payment">
<Spin spinning={loading}>
@@ -208,10 +250,7 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
{() => {
//If all of the job ids have been fileld in, then query and update the IP field.
const { payments } = form.getFieldsValue();
if (
payments?.length > 0 &&
payments?.filter((p) => p?.jobid).length === payments?.length
) {
if (payments?.length > 0 && payments?.filter((p) => p?.jobid).length === payments?.length) {
refetch({ jobids: payments.map((p) => p.jobid) });
}
return (
@@ -246,7 +285,6 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
const totalAmountToCharge = payments?.reduce((acc, val) => {
return acc + (val?.amount || 0);
}, 0);
return (
<Space style={{ float: "right" }}>
<Statistic title="Amount To Charge" value={totalAmountToCharge} precision={2} />
@@ -273,11 +311,36 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
>
{t("job_payments.buttons.proceedtopayment")}
</Button>
<Space direction="vertical" align="center">
<Button
type="primary"
// data-ipayname="submit"
className="ipayfield"
loading={queryLoading || loading}
disabled={!(totalAmountToCharge > 0)}
onClick={handleIntelliPayChargeShortLink}
>
{t("job_payments.buttons.create_short_link")}
</Button>
</Space>
</Space>
);
}}
</Form.Item>
</Form>
{paymentLink && (
<Space
style={{ cursor: "pointer", float: "right" }}
align="end"
onClick={() => {
navigator.clipboard.writeText(paymentLink);
message.success(t("general.actions.copied"));
}}
>
<div>{paymentLink}</div>
<CopyFilled />
</Space>
)}
</Spin>
</Card>
);

View File

@@ -13,7 +13,6 @@ import Icon, {
FileFilled,
HomeFilled,
ImportOutlined,
InfoCircleOutlined,
LineChartOutlined,
PaperClipOutlined,
PhoneOutlined,
@@ -27,8 +26,8 @@ import Icon, {
UserOutlined
} from "@ant-design/icons";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Layout, Menu, Switch, Tooltip } from "antd";
import React, { useEffect, useState } from "react";
import { Layout, Menu } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { BsKanban } from "react-icons/bs";
import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar, FaTasks } from "react-icons/fa";
@@ -43,7 +42,6 @@ import { selectRecentItems, selectSelectedHeader } from "../../redux/application
import { setModalContext } from "../../redux/modals/modals.actions";
import { signOutStart } from "../../redux/user/user.actions";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import { checkBeta, handleBeta, setBeta } from "../../utils/handleBeta";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
@@ -115,20 +113,22 @@ function Header({
names: ["ImEXPay", "DmsAp", "Simple_Inventory"],
splitKey: bodyshop && bodyshop.imexshopid
});
const [betaSwitch, setBetaSwitch] = useState(false);
const { t } = useTranslation();
useEffect(() => {
const isBeta = checkBeta();
setBetaSwitch(isBeta);
}, []);
const betaSwitchChange = (checked) => {
setBeta(checked);
setBetaSwitch(checked);
handleBeta();
const deleteBetaCookie = () => {
const cookieExists = document.cookie.split("; ").some((row) => row.startsWith(`betaSwitchImex=`));
if (cookieExists) {
const domain = window.location.hostname.split(".").slice(-2).join(".");
document.cookie = `betaSwitchImex=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.${domain}`;
console.log(`betaSwitchImex cookie deleted`);
} else {
console.log(`betaSwitchImex cookie does not exist`);
}
};
deleteBetaCookie();
const accountingChildren = [];
if (
@@ -695,31 +695,6 @@ function Header({
}
];
InstanceRenderManager({
executeFunction: true,
args: [],
imex: () => {
menuItems.push({
key: "beta-switch",
id: "header-beta-switch",
style: { marginLeft: "auto" },
label: (
<Tooltip
title={`A more modern ${InstanceRenderManager({
imex: t("titles.imexonline"),
rome: t("titles.romeonline"),
promanager: t("titles.promanager")
})} is ready for you to try! You can switch back at any time.`}
>
<InfoCircleOutlined />
<span style={{ marginRight: 8 }}>Try the new app</span>
<Switch checked={betaSwitch} onChange={betaSwitchChange} />
</Tooltip>
)
});
}
});
return (
<Layout.Header>
<Menu

View File

@@ -141,10 +141,14 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
key: t("jobs.fields.ded_amt"),
total: job.job_totals.totals.custPayable.deductible
},
// {
// key: t("jobs.fields.federal_tax_payable"),
// total: job.job_totals.totals.custPayable.federal_tax,
// },
...(InstanceRenderManager({
imex: [{
key: t("jobs.fields.federal_tax_payable"),
total: job.job_totals.totals.custPayable.federal_tax
}],
rome: [],
promanager: "USE_ROME"
})),
{
key: t("jobs.fields.other_amount_payable"),
total: job.job_totals.totals.custPayable.other_customer_amount

View File

@@ -27,6 +27,10 @@ export default function PartsOrderModalPriceChange({ form, field }) {
key: "25",
label: t("parts_orders.labels.discount", { percent: "25%" })
},
{
key: "40",
label: t("parts_orders.labels.discount", { percent: "40%" })
},
{
key: "custom",
label: (

View File

@@ -8,11 +8,12 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
bodyshop: selectBodyshop,
currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
@@ -20,7 +21,7 @@ const mapDispatchToProps = (dispatch) => ({
});
export default connect(mapStateToProps, mapDispatchToProps)(PaymentsGenerateLink);
export function PaymentsGenerateLink({ bodyshop, callback, job, openChatByPhone, setMessage }) {
export function PaymentsGenerateLink({ bodyshop, currentUser, callback, job, openChatByPhone, setMessage }) {
const { t } = useTranslation();
const [form] = Form.useForm();
@@ -30,29 +31,35 @@ export function PaymentsGenerateLink({ bodyshop, callback, job, openChatByPhone,
const handleFinish = async ({ amount }) => {
setLoading(true);
const p = parsePhoneNumber(job.ownr_ph1, "CA");
let p;
try {
p = parsePhoneNumber(job.ownr_ph1 || "", "CA");
} catch (error) {
console.log("Unable to parse phone number");
}
setLoading(true);
const response = await axios.post("/intellipay/generate_payment_url", {
bodyshop,
amount: amount,
account: job.ro_number,
invoice: job.id
comment: btoa(JSON.stringify({ payments: [{ jobid: job.id, amount }], userEmail: currentUser.email }))
});
setLoading(false);
setPaymentLink(response.data.shorUrl);
openChatByPhone({
phone_num: p.formatInternational(),
jobid: job.id
});
setMessage(
t("payments.labels.smspaymentreminder", {
shopname: bodyshop.shopname,
amount: amount,
payment_link: response.data.shorUrl
})
);
if (p) {
openChatByPhone({
phone_num: p.formatInternational(),
jobid: job.id
});
setMessage(
t("payments.labels.smspaymentreminder", {
shopname: bodyshop.shopname,
amount: amount,
payment_link: response.data.shorUrl
})
);
}
//Add in confirmation & errors.
if (callback) callback();

View File

@@ -6,11 +6,11 @@ import {
PauseCircleOutlined
} from "@ant-design/icons";
import { Card, Col, Row, Space, Tooltip } from "antd";
import Dinero from "dinero.js";
import React, { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import Dinero from "dinero.js";
import ProductionAlert from "../production-list-columns/production-list-columns.alert.component";
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
@@ -18,8 +18,8 @@ import ProductionSubletsManageComponent from "../production-sublets-manage/produ
import dayjs from "../../utils/day";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const cardColor = (ssbuckets, totalHrs) => {
const bucket = ssbuckets.find((bucket) => bucket.gte <= totalHrs && (!bucket.lt || bucket.lt > totalHrs));
@@ -213,21 +213,13 @@ const EstimatorToolTip = ({ metadata, cardSettings }) => {
};
const SubtotalTooltip = ({ metadata, cardSettings, t }) => {
const amount = metadata?.job_totals?.totals?.subtotal?.amount;
const dineroAmount = amount ? Dinero({ amount: parseInt(amount * 100) }).toFormat("0,0.00") : null;
const dineroAmount = Dinero(metadata?.job_totals?.totals?.subtotal ?? Dinero()).toFormat();
return (
cardSettings?.subtotal && (
<Col span={cardSettings.compact ? 24 : 12}>
<EllipsesToolTip
title={!!amount ? `${t("production.statistics.currency_symbol")}${dineroAmount}` : null}
kiosk={cardSettings.kiosk}
>
{!!amount ? (
<span>{`${t("production.statistics.currency_symbol")}${dineroAmount}`}</span>
) : (
<span>&nbsp;</span>
)}
<EllipsesToolTip title={`${dineroAmount}`} kiosk={cardSettings.kiosk}>
{dineroAmount}
</EllipsesToolTip>
</Col>
)

View File

@@ -3,6 +3,7 @@ import { Card, Statistic } from "antd";
import { useTranslation } from "react-i18next";
import PropTypes from "prop-types";
import { defaultKanbanSettings, statisticsItems } from "./settings/defaultKanbanSettings.js";
import Dinero from "dinero.js";
export const StatisticType = {
HOURS: "hours",
@@ -32,7 +33,21 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
};
const calculateTotalAmount = (items, key) => {
return items.reduce((acc, item) => acc + (item[key]?.totals?.subtotal?.amount || 0), 0);
return items.reduce((acc, item) => acc.add(Dinero(item[key]?.totals?.subtotal ?? Dinero())), Dinero({ amount: 0 }));
};
const calculateReducerTotalAmount = (lanes, key) => {
return lanes.reduce(
(acc, lane) => {
return acc.add(
lane.cards.reduce(
(laneAcc, card) => laneAcc.add(Dinero(card.metadata[key]?.totals?.subtotal ?? Dinero())),
Dinero({ amount: 0 })
)
);
},
Dinero({ amount: 0 })
);
};
const calculateReducerTotal = (lanes, key, subKey) => {
@@ -43,14 +58,6 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
}, 0);
};
const calculateReducerTotalAmount = (lanes, key) => {
return lanes.reduce((acc, lane) => {
return (
acc + lane.cards.reduce((laneAcc, card) => laneAcc + (card.metadata[key]?.totals?.subtotal?.amount || 0), 0)
);
}, 0);
};
const formatValue = (value, type) => {
if (type === StatisticType.JOBS) {
return value.toFixed(0);
@@ -87,9 +94,15 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
const totalAmountInProduction = useMemo(() => {
if (!cardSettings.totalAmountInProduction) return null;
const total = calculateTotalAmount(data, "job_totals");
return parseFloat(total.toFixed(2));
return total.toFormat("$0,0.00");
}, [data, cardSettings.totalAmountInProduction]);
const totalAmountOnBoard = useMemo(() => {
if (!reducerData || !cardSettings.totalAmountOnBoard) return null;
const total = calculateReducerTotalAmount(reducerData.lanes, "job_totals");
return total.toFormat("$0,0.00");
}, [reducerData, cardSettings.totalAmountOnBoard]);
const totalHrsOnBoard = useMemo(() => {
if (!reducerData || !cardSettings.totalHrsOnBoard) return null;
const total =
@@ -118,12 +131,6 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
[reducerData, cardSettings.jobsOnBoard]
);
const totalAmountOnBoard = useMemo(() => {
if (!reducerData || !cardSettings.totalAmountOnBoard) return null;
const total = calculateReducerTotalAmount(reducerData.lanes, "job_totals");
return parseFloat(total.toFixed(2));
}, [reducerData, cardSettings.totalAmountOnBoard]);
const tasksInProduction = useMemo(() => {
if (!data || !cardSettings.tasksInProduction) return null;
return data.reduce((acc, item) => acc + (item.tasks_aggregate?.aggregate?.count || 0), 0);
@@ -191,7 +198,6 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
<Statistic
title={t(`production.statistics.${stat.label}`)}
value={formatValue(stat.value, stat.type)}
prefix={stat.type === StatisticType.AMOUNT ? t("production.statistics.currency_symbol") : undefined}
suffix={
stat.type === StatisticType.HOURS
? t("production.statistics.hours")

View File

@@ -34,28 +34,34 @@ export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) {
const [form] = Form.useForm();
const [search, setSearch] = useState("");
const {
treatments: { Enhanced_Payroll }
treatments: { Enhanced_Payroll, ADPPayroll }
} = useSplitTreatments({
attributes: {},
names: ["Enhanced_Payroll"],
names: ["Enhanced_Payroll", "ADPPayroll"],
splitKey: bodyshop.imexshopid
});
const [loading, setLoading] = useState(false);
const { t } = useTranslation();
const Templates = TemplateList("report_center");
const ReportsList =
Enhanced_Payroll.treatment === "on"
? Object.keys(Templates)
.map((key) => {
return Templates[key];
})
.filter((temp) => temp.enhanced_payroll === undefined || temp.enhanced_payroll === true)
: Object.keys(Templates)
.map((key) => {
return Templates[key];
})
.filter((temp) => temp.enhanced_payroll === undefined || temp.enhanced_payroll === false);
const ReportsList = Object.keys(Templates)
.map((key) => Templates[key])
.filter((temp) => {
const enhancedPayrollOn = Enhanced_Payroll.treatment === "on";
const adpPayrollOn = ADPPayroll.treatment === "on";
if (enhancedPayrollOn && adpPayrollOn) {
return temp.enhanced_payroll !== false || temp.adp_payroll !== false;
}
if (enhancedPayrollOn) {
return temp.enhanced_payroll !== false && temp.adp_payroll !== true;
}
if (adpPayrollOn) {
return temp.adp_payroll !== false && temp.enhanced_payroll !== true;
}
return temp.enhanced_payroll !== true && temp.adp_payroll !== true;
});
const { open } = reportCenterModal;
@@ -104,7 +110,7 @@ export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) {
to: values.to,
subject: Templates[values.key]?.subject
},
values.sendbyexcel === "excel" ? "x" : values.sendby === "email" ? "e" : "p",
values.sendbytext === "text" ? values.sendbytext : values.sendbyexcel === "excel" ? "x" : values.sendby === "email" ? "e" : "p",
id
);
setLoading(false);
@@ -291,7 +297,15 @@ export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) {
</Radio.Group>
</Form.Item>
);
if (reporttype !== "excel")
if (reporttype === "text")
return (
<Form.Item label={t("general.labels.sendby")} name="sendbytext" initialValue="text">
<Radio.Group>
<Radio value="text">{t("general.labels.text")}</Radio>
</Radio.Group>
</Form.Item>
);
if (reporttype !== "excel" || reporttype !== "text")
return (
<Form.Item label={t("general.labels.sendby")} name="sendby" initialValue="print">
<Radio.Group>

View File

@@ -20,6 +20,7 @@ import ShopInfoTaskPresets from "./shop-info.task-presets.component";
import queryString from "query-string";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import ShopInfoRoGuard from "./shop-info.roguard.component";
import ShopInfoIntellipay from "./shop-intellipay-config.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
@@ -135,6 +136,17 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
],
rome: "USE_IMEX",
promanager: []
}),
...InstanceRenderManager({
imex: [],
rome: [
{
key: "intellipay",
label: t("bodyshop.labels.intellipay"),
children: <ShopInfoIntellipay form={form} />
}
],
promanager: []
})
];
return (

View File

@@ -7,13 +7,13 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import DatePickerRanges from "../../utils/DatePickerRanges";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import FeatureWrapper, { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import FormItemEmail from "../form-items-formatted/email-form-item.component";
import PhoneFormItem, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
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";
import FeatureWrapper, { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
// TODO: Client Update, this might break
const timeZonesList = Intl.supportedValuesOf("timeZone");
const mapStateToProps = createStructuredSelector({
@@ -28,10 +28,10 @@ export function ShopInfoGeneral({ form, bodyshop }) {
const { t } = useTranslation();
const {
treatments: { ClosingPeriod }
treatments: { ClosingPeriod, ADPPayroll }
} = useSplitTreatments({
attributes: {},
names: ["ClosingPeriod"],
names: ["ClosingPeriod", "ADPPayroll"],
splitKey: bodyshop && bodyshop.imexshopid
});
@@ -98,7 +98,6 @@ export function ShopInfoGeneral({ form, bodyshop }) {
<Form.Item label={t("bodyshop.fields.email")} name="email">
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.phone")}
name="phone"
@@ -356,14 +355,22 @@ export function ShopInfoGeneral({ form, bodyshop }) {
<Select mode="tags" />
</Form.Item>
{ClosingPeriod.treatment === "on" && (
<>
<Form.Item
name={["accountingconfig", "ClosingPeriod"]}
label={t("bodyshop.fields.closingperiod")} //{t("reportcenter.labels.dates")}
>
<DatePicker.RangePicker format="MM/DD/YYYY" presets={DatePickerRanges} />
</Form.Item>
</>
<Form.Item
name={["accountingconfig", "ClosingPeriod"]}
label={t("bodyshop.fields.closingperiod")} //{t("reportcenter.labels.dates")}
>
<DatePicker.RangePicker format="MM/DD/YYYY" presets={DatePickerRanges} />
</Form.Item>
)}
{ADPPayroll.treatment === "on" && (
<Form.Item name={["accountingconfig", "companyCode"]} label={t("bodyshop.fields.companycode")}>
<Input />
</Form.Item>
)}
{ADPPayroll.treatment === "on" && (
<Form.Item name={["accountingconfig", "batchID"]} label={t("bodyshop.fields.batchid")}>
<Input />
</Form.Item>
)}
</LayoutFormRow>
</FeatureWrapper>

View File

@@ -30,219 +30,226 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
return (
<RbacWrapper action="shop:rbac">
<LayoutFormRow>
{...HasFeatureAccess({ featureName: "export", bodyshop }) ? [
<Form.Item
label={t("bodyshop.fields.rbac.accounting.exportlog")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:exportlog"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.accounting.payables")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:payables"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.accounting.payments")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:payments"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.accounting.receivables")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:receivables"]}
>
<InputNumber />
</Form.Item>
]:[]}
{...HasFeatureAccess({ featureName: "bills", bodyshop }) ? [
<Form.Item
label={t("bodyshop.fields.rbac.bills.delete")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:delete"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.bills.enter")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:enter"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.bills.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:list"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.bills.reexport")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:reexport"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.bills.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:view"]}
>
<InputNumber />
</Form.Item>
]:[]}
{...HasFeatureAccess({ featureName: "courtesycars", bodyshop }) ? [
<Form.Item
label={t("bodyshop.fields.rbac.contracts.create")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "contracts:create"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.contracts.detail")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "contracts:detail"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.contracts.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "contracts:list"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.courtesycar.create")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "courtesycar:create"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.courtesycar.detail")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "courtesycar:detail"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.courtesycar.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "courtesycar:list"]}
>
<InputNumber />
</Form.Item>
]:[]}
{...HasFeatureAccess({ featureName: "csi", bodyshop }) ? [
<Form.Item
label={t("bodyshop.fields.rbac.csi.export")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "csi:export"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.csi.page")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "csi:page"]}
>
<InputNumber />
</Form.Item>
]:[]}
{...HasFeatureAccess({ featureName: "export", bodyshop })
? [
<Form.Item
label={t("bodyshop.fields.rbac.accounting.exportlog")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:exportlog"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.accounting.payables")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:payables"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.accounting.payments")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:payments"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.accounting.receivables")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:receivables"]}
>
<InputNumber />
</Form.Item>
]
: []}
{...HasFeatureAccess({ featureName: "bills", bodyshop })
? [
<Form.Item
label={t("bodyshop.fields.rbac.bills.delete")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:delete"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.bills.enter")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:enter"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.bills.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:list"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.bills.reexport")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:reexport"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.bills.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:view"]}
>
<InputNumber />
</Form.Item>
]
: []}
{...HasFeatureAccess({ featureName: "courtesycars", bodyshop })
? [
<Form.Item
label={t("bodyshop.fields.rbac.contracts.create")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "contracts:create"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.contracts.detail")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "contracts:detail"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.contracts.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "contracts:list"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.courtesycar.create")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "courtesycar:create"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.courtesycar.detail")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "courtesycar:detail"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.courtesycar.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "courtesycar:list"]}
>
<InputNumber />
</Form.Item>
]
: []}
{...HasFeatureAccess({ featureName: "csi", bodyshop })
? [
<Form.Item
label={t("bodyshop.fields.rbac.csi.export")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "csi:export"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.csi.page")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "csi:page"]}
>
<InputNumber />
</Form.Item>
]
: []}
<Form.Item
label={t("bodyshop.fields.rbac.employees.page")}
rules={[
@@ -255,6 +262,18 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.employee_teams.page")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "employee_teams:page"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.admin")}
rules={[
@@ -435,31 +454,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.employees.page")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "employees:page"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.employee_teams.page")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "employee_teams:page"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.payments.enter")}
rules={[
@@ -522,7 +516,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
<InputNumber />
</Form.Item>
)}
<Form.Item
label={t("bodyshop.fields.rbac.production.list")}
rules={[
@@ -561,128 +554,118 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
<InputNumber />
</Form.Item>
)}
{...HasFeatureAccess({ featureName: "timetickets", bodyshop }) ? [
<Form.Item
label={t("bodyshop.fields.rbac.shiftclock.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "shiftclock:view"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.shop.config")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "shop:config"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.edit")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:edit"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.shiftedit")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:shiftedit"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.editcommitted")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:editcommitted"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.ttapprovals.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "ttapprovals:view"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.ttapprovals.approve")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "ttapprovals:approve"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.enter")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:enter"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:list"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.shiftedit")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:shiftedit"]}
>
<InputNumber />
</Form.Item>
]:[]}
{...HasFeatureAccess({ featureName: "timetickets", bodyshop })
? [
<Form.Item
label={t("bodyshop.fields.rbac.shiftclock.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "shiftclock:view"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.shop.config")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "shop:config"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.edit")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:edit"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.shiftedit")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:shiftedit"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.editcommitted")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:editcommitted"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.ttapprovals.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "ttapprovals:view"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.ttapprovals.approve")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "ttapprovals:approve"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.enter")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:enter"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:list"]}
>
<InputNumber />
</Form.Item>
]
: []}
<Form.Item
label={t("bodyshop.fields.rbac.shop.vendors")}
rules={[
@@ -757,7 +740,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
<InputNumber />
</Form.Item>
)}
<Form.Item
label={t("bodyshop.fields.rbac.users.editaccess")}
rules={[

View File

@@ -0,0 +1,54 @@
import { Alert, Form, InputNumber, Switch } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoIntellipay);
export function ShopInfoIntellipay({ bodyshop, form }) {
const { t } = useTranslation();
return (
<>
<Form.Item dependencies={[["intellipay_config", "enable_cash_discount"]]}>
{() => {
const { intellipay_config } = form.getFieldsValue();
if (intellipay_config?.enable_cash_discount)
return <Alert message={t("bodyshop.labels.intellipay_cash_discount")} />;
}}
</Form.Item>
<LayoutFormRow noDivider>
<Form.Item
label={t("bodyshop.fields.intellipay_config.enable_cash_discount")}
valuePropName="checked"
name={["intellipay_config", "enable_cash_discount"]}
>
<Switch />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.intellipay_config.cash_discount_percentage")}
valuePropName="checked"
dependencies={[["intellipay_config", "enable_cash_discount"]]}
name={["intellipay_config", "cash_discount_percentage"]}
rules={[
({ getFieldsValue }) => ({ required: form.getFieldValue(["intellipay_config", "enable_cash_discount"]) })
]}
>
<InputNumber min={0} max={100} precision={1} suffix='%'/>
</Form.Item>
</LayoutFormRow>
</>
);
}

View File

@@ -12,7 +12,7 @@ import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
import { onlyUnique } from "../../utils/arrayHelper";
import dayjs from "../../utils/day";
import { alphaSort, dateSort } from "../../utils/sorters";
import RbacWrapper, { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component";
const mapStateToProps = createStructuredSelector({
@@ -52,6 +52,10 @@ export function TimeTicketList({
splitKey: bodyshop.imexshopid
});
const canEditCommittedTimeTickets = HasRbacAccess({ bodyshop, authLevel, action: "timetickets:editcommitted" });
const canEditTimeTickets = HasRbacAccess({ bodyshop, authLevel, action: "timetickets:edit" });
const canEditShiftTickets = HasRbacAccess({ bodyshop, authLevel, action: "timetickets:shiftedit" });
const totals = useMemo(() => {
if (timetickets)
return timetickets.reduce(
@@ -65,6 +69,18 @@ export function TimeTicketList({
return { productivehrs: 0, actualhrs: 0 };
}, [timetickets]);
const isDisabled = (record) => {
if (disabled === true || !record.id) return true;
const isShiftTicket = !record.ciecacode;
const isCommitted = record.committed_at;
if (isShiftTicket) {
return !(canEditShiftTickets && (!isCommitted || canEditCommittedTimeTickets));
}
return !(canEditTimeTickets && (!isCommitted || canEditCommittedTimeTickets));
};
const columns = [
...(Enhanced_Payroll.treatment === "on"
? [
@@ -241,59 +257,16 @@ export function TimeTicketList({
</TimeTicketEnterButton>
)}
{!techConsole && (
<RbacWrapper
action="timetickets:edit"
noauth={() => {
return <div />;
<TimeTicketEnterButton
actions={{ refetch }}
context={{
id: record.id,
timeticket: record
}}
disabled={isDisabled(record)}
>
<TimeTicketEnterButton
actions={{ refetch }}
context={{
id: record.id,
timeticket: record
}}
disabled={
record.ciecacode
? record.committed_at
? HasRbacAccess({
bodyshop,
authLevel: authLevel,
action: "timetickets:editcommitted"
}) &&
HasRbacAccess({
bodyshop,
authLevel: authLevel,
action: "timetickets:edit"
})
: HasRbacAccess({
bodyshop,
authLevel: authLevel,
action: "timetickets:edit"
})
: record.committed_at
? HasRbacAccess({
bodyshop,
authLevel: authLevel,
action: "timetickets:editcommitted"
}) &&
HasRbacAccess({
bodyshop,
authLevel: authLevel,
action: "timetickets:shiftedit"
})
: HasRbacAccess({
bodyshop,
authLevel: authLevel,
action: "timetickets:shiftedit"
})
? disabled
: !record.jobid
}
>
<EditFilled />
</TimeTicketEnterButton>
</RbacWrapper>
<EditFilled />
</TimeTicketEnterButton>
)}
</Space>
)

View File

@@ -329,7 +329,9 @@ export function LaborAllocationContainer({ jobid, loading, lineTicketData, hideT
timetickets={lineTicketData.timetickets}
adjustments={lineTicketData.jobs_by_pk.lbr_adjustments}
/>
{!hideTimeTickets && <TimeTicketList loading={loading} timetickets={lineTicketData.timetickets} techConsole />}
{!hideTimeTickets && (
<TimeTicketList loading={loading} timetickets={jobid ? lineTicketData.timetickets : []} techConsole />
)}
</div>
);
}

View File

@@ -1,13 +1,14 @@
import { AlertOutlined } from "@ant-design/icons";
import { Alert, Button, Col, Row, Space } from "antd";
import { Alert, Button, Col, notification, Row, Space } from "antd";
import i18n from "i18next";
import React, { useEffect } from "react";
import React, { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectUpdateAvailable } from "../../redux/application/application.selectors";
import { useRegisterSW } from "virtual:pwa-register/react";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import useCountDown from "../../utils/countdownHook";
const mapStateToProps = createStructuredSelector({
updateAvailable: selectUpdateAvailable
@@ -19,6 +20,15 @@ const mapDispatchToProps = (dispatch) => ({
export function UpdateAlert({ updateAvailable }) {
const { t } = useTranslation();
const [timerStarted, setTimerStarted] = useState(false);
const [loading, setLoading] = useState(false);
const [
timeLeft,
{
start //pause, resume, reset
}
] = useCountDown(180000, 1000);
const {
offlineReady: [offlineReady],
needRefresh: [needRefresh],
@@ -31,7 +41,7 @@ export function UpdateAlert({ updateAvailable }) {
() => {
r.update();
},
10 * 60 * 1000
30 * 60 * 1000
);
}
},
@@ -40,11 +50,43 @@ export function UpdateAlert({ updateAvailable }) {
}
});
const ReloadNewVersion = useCallback(() => {
setLoading(true);
updateServiceWorker(true);
setTimeout(() => {
window.location.reload(true);
}, 5000);
}, [updateServiceWorker]);
useEffect(() => {
if (import.meta.env.DEV) {
console.log(`SW Status => Refresh? ${needRefresh} - offlineReady? ${offlineReady}`);
if (needRefresh) {
start();
setTimerStarted(true);
}
}, [needRefresh, offlineReady]);
}, [start, needRefresh, offlineReady]);
useEffect(() => {
if (needRefresh && timerStarted && timeLeft < 60000) {
notification.open({
type: "warning",
closable: false,
duration: 65000,
key: "autoupdate",
message: t("general.actions.autoupdate", {
time: (timeLeft / 1000).toFixed(0),
app: InstanceRenderManager({
imex: "$t(titles.imexonline)",
rome: "$t(titles.romeonline)",
promanager: "$t(titles.promanager)"
})
}),
placement: "bottomRight"
});
}
if (needRefresh && timerStarted && timeLeft <= 0) {
ReloadNewVersion();
}
}, [timeLeft, t, needRefresh, ReloadNewVersion, timerStarted]);
if (!needRefresh) return null;
@@ -75,9 +117,10 @@ export function UpdateAlert({ updateAvailable }) {
<Button onClick={() => window.open("https://imex-online.noticeable.news/", "_blank")}>
{i18n.t("general.actions.viewreleasenotes")}
</Button>
<Button type="primary" onClick={() => updateServiceWorker(true)}>
{i18n.t("general.actions.refresh")}
<Button loading={loading} type="primary" onClick={() => ReloadNewVersion()}>
{i18n.t("general.actions.refresh")} {`(${(timeLeft / 1000).toFixed(0)} s)`}
</Button>
<Button onClick={() => start(300000)}>{i18n.t("general.actions.delay")}</Button>
</Space>
</Col>
</Row>

View File

@@ -5,7 +5,7 @@ import PhoneNumberFormatter from "../../utils/PhoneFormatter";
const { Option } = Select;
//To be used as a form element only.
// To be used as a form element only.
const VendorSearchSelect = ({ value, onChange, options, onSelect, disabled, preferredMake, showPhone }, ref) => {
const [option, setOption] = useState(value);
@@ -33,9 +33,25 @@ const VendorSearchSelect = ({ value, onChange, options, onSelect, disabled, pref
if (!value || !options) return label;
const discount = options?.find((o) => o.id === value)?.discount;
return (
<div className="imex-flex-row" style={{ width: "100%" }}>
<div style={{ flex: 1 }}>{label}</div>
<div
style={{
display: "flex",
alignItems: "center",
flexWrap: "nowrap",
width: "100%"
}}
>
<div
style={{
flex: 1,
minWidth: 0,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap"
}}
>
{label}
</div>
{discount && discount !== 0 ? <Tag color="green">{`${discount * 100}%`}</Tag> : null}
</div>
);
@@ -45,36 +61,67 @@ const VendorSearchSelect = ({ value, onChange, options, onSelect, disabled, pref
optionFilterProp="name"
onSelect={onSelect}
disabled={disabled || false}
optionLabelProp={"name"}
optionLabelProp="name"
>
{favorites
? favorites.map((o) => (
<Option key={`favorite-${o.id}`} value={o.id} name={o.name} discount={o.discount}>
<div className="imex-flex-row">
<div style={{ flex: 1 }}>{o.name}</div>
<Space style={{ marginLeft: "1rem" }}>
<HeartOutlined style={{ color: "red" }} />
{o.phone && showPhone && <PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>}
{o.discount && o.discount !== 0 ? <Tag color="green">{`${o.discount * 100}%`}</Tag> : null}
</Space>
{favorites &&
favorites.map((o) => (
<Option key={`favorite-${o.id}`} value={o.id} name={o.name} discount={o.discount}>
<div
style={{
display: "flex",
alignItems: "center",
flexWrap: "nowrap",
width: "100%"
}}
>
<div
style={{
flex: 1,
minWidth: 0,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap"
}}
>
{o.name}
</div>
</Option>
))
: null}
{options
? options.map((o) => (
<Option key={o.id} value={o.id} name={o.name} discount={o.discount}>
<div className="imex-flex-row" style={{ width: "100%" }}>
<div style={{ flex: 1 }}>{o.name}</div>
<Space style={{ marginLeft: "1rem" }}>
{o.phone && showPhone && <PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>}
{o.discount && o.discount !== 0 ? <Tag color="green">{`${o.discount * 100}%`}</Tag> : null}
</Space>
<Space style={{ marginLeft: "1rem" }}>
<HeartOutlined style={{ color: "red" }} />
{o.phone && showPhone && <PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>}
{o.discount && o.discount !== 0 ? <Tag color="green">{`${o.discount * 100}%`}</Tag> : null}
</Space>
</div>
</Option>
))}
{options &&
options.map((o) => (
<Option key={o.id} value={o.id} name={o.name} discount={o.discount}>
<div
style={{
display: "flex",
alignItems: "center",
flexWrap: "nowrap",
width: "100%"
}}
>
<div
style={{
flex: 1,
minWidth: 0,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap"
}}
>
{o.name}
</div>
</Option>
))
: null}
<Space style={{ marginLeft: "1rem" }}>
{o.phone && showPhone && <PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>}
{o.discount && o.discount !== 0 ? <Tag color="green">{`${o.discount * 100}%`}</Tag> : null}
</Space>
</div>
</Option>
))}
</Select>
);
};

View File

@@ -5,7 +5,6 @@ import { getFirestore } from "firebase/firestore";
import { getMessaging, getToken, onMessage } from "firebase/messaging";
import { store } from "../redux/store";
import axios from "axios";
import { checkBeta } from "../utils/handleBeta";
const config = JSON.parse(import.meta.env.VITE_APP_FIREBASE_CONFIG);
initializeApp(config);
@@ -88,7 +87,7 @@ export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
operationName: eventName,
variables: additionalParams,
dbevent: false,
env: checkBeta() ? "beta" : "master"
env: "master"
});
// console.log(
// "%c[Analytics]",

View File

@@ -138,7 +138,8 @@ export const QUERY_BODYSHOP = gql`
tt_enforce_hours_for_tech_console
md_tasks_presets
use_paint_scale_data
md_ro_guard
intellipay_config
md_ro_guard
employee_teams(order_by: { name: asc }, where: { active: { _eq: true } }) {
id
name
@@ -266,7 +267,8 @@ export const UPDATE_SHOP = gql`
enforce_conversion_category
tt_enforce_hours_for_tech_console
md_tasks_presets
md_ro_guard
intellipay_config
md_ro_guard
employee_teams(order_by: { name: asc }, where: { active: { _eq: true } }) {
id
name

View File

@@ -1,21 +1,21 @@
import { Tabs } from "antd";
import React, { useEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import queryString from "query-string";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation, useNavigate } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import ShopCsiConfig from "../../components/shop-csi-config/shop-csi-config.component";
import ShopEmployeesContainer from "../../components/shop-employees/shop-employees.container";
import ShopInfoContainer from "../../components/shop-info/shop-info.container";
import ShopCsiConfig from "../../components/shop-csi-config/shop-csi-config.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import ShopInfoUsersComponent from "../../components/shop-users/shop-users.component";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import ShopInfoUsersComponent from "../../components/shop-users/shop-users.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import ShopTeamsContainer from "../../components/shop-teams/shop-teams.container";
import { HasFeatureAccess } from "../../components/feature-wrapper/feature-wrapper.component";
import ShopTeamsContainer from "../../components/shop-teams/shop-teams.container";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop

View File

@@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
import RbacWrapperComponent from "../../components/rbac-wrapper/rbac-wrapper.component";
import TechLookupJobsList from "../../components/tech-lookup-jobs-list/tech-lookup-jobs-list.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import TechLookupJobsDrawer from "../../components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component";
export default function TechLookupContainer() {
const { t } = useTranslation();
@@ -20,6 +21,7 @@ export default function TechLookupContainer() {
return (
<div>
<RbacWrapperComponent action="jobs:list-active">
<TechLookupJobsDrawer />
<TechLookupJobsList />
</RbacWrapperComponent>
</div>

View File

@@ -9,7 +9,6 @@ import ErrorBoundary from "../../components/error-boundary/error-boundary.compon
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import TechHeader from "../../components/tech-header/tech-header.component";
import TechLookupJobsDrawer from "../../components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component";
import TechSider from "../../components/tech-sider/tech-sider.component";
import UpdateAlert from "../../components/update-alert/update-alert.component";
import { selectTechnician } from "../../redux/tech/tech.selectors";
@@ -68,7 +67,7 @@ export function TechPage({ technician }) {
<Layout>
<UpdateAlert />
<TechHeader />
<TechLookupJobsDrawer />
<TaskUpsertModalContainer />
<Content className="tech-content-container">
<ErrorBoundary>

View File

@@ -10,7 +10,7 @@ import {
signInWithEmailAndPassword,
signOut
} from "firebase/auth";
import { doc, getDoc, setDoc } from "firebase/firestore";
import { arrayUnion, doc, getDoc, setDoc, updateDoc } from "firebase/firestore";
import { getToken } from "firebase/messaging";
import i18next from "i18next";
import LogRocket from "logrocket";
@@ -48,6 +48,7 @@ import {
validatePasswordResetSuccess
} from "./user.actions";
import UserActionTypes from "./user.types";
import cleanAxios from "../../utils/CleanAxios";
const fpPromise = FingerprintJS.load();
@@ -177,10 +178,24 @@ export function* setInstanceIdSaga({ payload: uid }) {
// Get the visitor identifier when you need it.
const fp = yield fpPromise;
const result = yield fp.get();
yield setDoc(userInstanceRef, {
timestamp: new Date(),
fingerprint: result.visitorId
});
const res = yield cleanAxios.get("https://api.ipify.org/?format=json");
const udoc = yield getDoc(userInstanceRef);
if (!udoc.data()) {
yield setDoc(userInstanceRef, {
timestamp: new Date(),
fingerprint: result.visitorId,
//totalFingerprint: result,
ip: [res.data.ip]
});
} else {
yield updateDoc(userInstanceRef, {
timestamp: new Date(),
fingerprint: result.visitorId,
//totalFingerprint: result,
ip: arrayUnion(res.data.ip)
});
}
yield put(setLocalFingerprint(result.visitorId));
yield delay(5 * 60 * 1000);

View File

@@ -230,7 +230,7 @@
"markexported": "Mark Exported",
"markforreexport": "Mark for Re-export",
"new": "New Bill",
"nobilllines": "This part has not yet been recieved.",
"nobilllines": "",
"noneselected": "No bill selected.",
"onlycmforinvoiced": "Only credit memos can be entered for any Job that has been invoiced, exported, or voided.",
"printlabels": "Print Labels",
@@ -270,9 +270,9 @@
"testrender": "Test Render"
},
"errors": {
"creatingdefaultview": "Error creating default view.",
"loading": "Unable to load shop details. Please call technical support.",
"saving": "Error encountered while saving. {{message}}",
"creatingdefaultview": "Error creating default view."
"saving": "Error encountered while saving. {{message}}"
},
"fields": {
"ReceivableCustomField": "QBO Receivable Custom Field {{number}}",
@@ -285,19 +285,21 @@
},
"appt_length": "Default Appointment Length",
"attach_pdf_to_email": "Attach PDF copy to sent emails?",
"batchid": "ADP Batch ID",
"bill_allow_post_to_closed": "Allow Bills to be posted to Closed Jobs",
"bill_federal_tax_rate": "Bills - Federal Tax Rate %",
"bill_local_tax_rate": "Bill - Local Tax Rate %",
"bill_state_tax_rate": "Bill - Provincial/State Tax Rate %",
"city": "City",
"closingperiod": "Closing Period",
"companycode": "ADP Company Code",
"country": "Country",
"dailybodytarget": "Scoreboard - Daily Body Target",
"dailypainttarget": "Scoreboard - Daily Paint Target",
"default_adjustment_rate": "Default Labor Deduction Adjustment Rate",
"deliver": {
"templates": "Delivery Templates",
"require_actual_delivery_date": "Require Actual Delivery"
"require_actual_delivery_date": "Require Actual Delivery",
"templates": "Delivery Templates"
},
"dms": {
"apcontrol": "AP Control Number",
@@ -330,6 +332,10 @@
"next_contact_hours": "Automatic Next Contact Date - Hours from Intake",
"templates": "Intake Templates"
},
"intellipay_config": {
"cash_discount_percentage": "Cash Discount %",
"enable_cash_discount": "Enable Cash Discounting"
},
"invoice_federal_tax_rate": "Invoices - Federal Tax Rate",
"invoice_local_tax_rate": "Invoices - Local Tax Rate",
"invoice_state_tax_rate": "Invoices - State Tax Rate",
@@ -661,6 +667,8 @@
"filehandlers": "Adjusters",
"insurancecos": "Insurance Companies",
"intakechecklist": "Intake Checklist",
"intellipay": "IntelliPay",
"intellipay_cash_discount": "Please ensure that cash discounting has been enabled on your merchant account. Reach out to IntelliPay Support if you need assistance. ",
"jobstatuses": "Job Statuses",
"laborrates": "Labor Rates",
"licensing": "Licensing",
@@ -700,10 +708,10 @@
"workingdays": "Working Days"
},
"successes": {
"save": "Shop configuration saved successfully. ",
"unsavedchanges": "Unsaved changes will be lost. Are you sure you want to continue?",
"areyousure": "Are you sure you want to continue?",
"defaultviewcreated": "Default view created successfully."
"defaultviewcreated": "Default view created successfully.",
"save": "Shop configuration saved successfully. ",
"unsavedchanges": "Unsaved changes will be lost. Are you sure you want to continue?"
},
"validation": {
"centermustexist": "The chosen responsibility center does not exist.",
@@ -1133,8 +1141,8 @@
},
"general": {
"actions": {
"defaults": "Defaults",
"add": "Add",
"autoupdate": "{{app}} will automatically update in {{time}} seconds. Please save all changes.",
"calculate": "Calculate",
"cancel": "Cancel",
"clear": "Clear",
@@ -1142,6 +1150,8 @@
"copied": "Copied!",
"copylink": "Copy Link",
"create": "Create",
"defaults": "Defaults",
"delay": "Delay Update (5 mins)",
"delete": "Delete",
"deleteall": "Delete All",
"deselectall": "Deselect All",
@@ -1153,10 +1163,12 @@
"print": "Print",
"refresh": "Refresh",
"remove": "Remove",
"remove_alert": "Are you sure you want to dismiss the alert?",
"reset": "Reset your changes.",
"resetpassword": "Reset Password",
"save": "Save",
"saveandnew": "Save and New",
"saveas": "Save As",
"selectall": "Select All",
"send": "Send",
"sendbysms": "Send by SMS",
@@ -1164,9 +1176,7 @@
"submit": "Submit",
"tryagain": "Try Again",
"view": "View",
"viewreleasenotes": "See What's Changed",
"remove_alert": "Are you sure you want to dismiss the alert?",
"saveas": "Save As"
"viewreleasenotes": "See What's Changed"
},
"errors": {
"fcm": "You must allow notification permissions to have real time messaging. Click to try again.",
@@ -1181,7 +1191,6 @@
"vehicle": "Vehicle"
},
"labels": {
"unsavedchanges": "Unsaved changes.",
"actions": "Actions",
"areyousure": "Are you sure?",
"barcode": "Barcode",
@@ -1250,6 +1259,7 @@
"tuesday": "Tuesday",
"tvmode": "TV Mode",
"unknown": "Unknown",
"unsavedchanges": "Unsaved changes.",
"username": "Username",
"view": "View",
"wednesday": "Wednesday",
@@ -1363,6 +1373,7 @@
},
"job_payments": {
"buttons": {
"create_short_link": "Generate Short Link",
"goback": "Go Back",
"proceedtopayment": "Proceed to Payment",
"refundpayment": "Refund Payment"
@@ -2739,41 +2750,6 @@
}
},
"production": {
"constants": {
"main_profile": "Default"
},
"options": {
"small": "Small",
"medium": "Medium",
"large": "Large",
"vertical": "Vertical",
"horizontal": "Horizontal"
},
"settings": {
"layout": "Layout",
"information": "Information",
"statistics_title": "Statistics",
"board_settings": "Board Settings",
"filters_title": "Filters",
"filters": {
"md_ins_cos": "Insurance Companies",
"md_estimators": "Estimators"
},
"statistics": {
"total_hours_in_production": "Hours in Production",
"total_lab_in_production": "Body Hours in Production",
"total_lar_in_production": "Refinish Hours in Production",
"total_amount_in_production": "Dollars in Production",
"jobs_in_production": "Jobs in Production",
"total_hours_on_board": "Hours on Board",
"total_lab_on_board": "Body Hours on Board",
"total_lar_on_board": "Refinish Hours on Board",
"total_amount_on_board": "Dollars on Board",
"total_jobs_on_board": "Jobs on Board",
"tasks_in_production": "Tasks in Production",
"tasks_on_board": "Tasks on Board"
}
},
"actions": {
"addcolumns": "Add Columns",
"bodypriority-clear": "Clear Body Priority",
@@ -2788,29 +2764,23 @@
"suspend": "Suspend",
"unsuspend": "Unsuspend"
},
"constants": {
"main_profile": "Default"
},
"errors": {
"boardupdate": "Error encountered updating Job. {{message}}",
"removing": "Error removing from production board. {{error}}",
"settings": "Error saving board settings: {{error}}",
"name_exists": "A Profile with this name already exists. Please choose a different name.",
"name_required": "Profile name is required."
"name_required": "Profile name is required.",
"removing": "Error removing from production board. {{error}}",
"settings": "Error saving board settings: {{error}}"
},
"labels": {
"kiosk_mode": "Kiosk Mode",
"on": "On",
"off": "Off",
"wide": "Wide",
"tall": "Tall",
"vertical": "Vertical",
"horizontal": "Horizontal",
"orientation": "Board Orientation",
"card_size": "Card Size",
"model_info": "Vehicle Info",
"actual_in": "Actual In",
"addnewprofile": "Add New Profile",
"alert": "Alert",
"tasks": "Tasks",
"alertoff": "Remove alert from Job",
"alerton": "Add alert to Job",
"alerts": "Alerts",
"ats": "Alternative Transportation",
"bodyhours": "B",
"bodypriority": "B/P",
@@ -2820,6 +2790,7 @@
"qbo_usa": "QBO USA"
}
},
"card_size": "Card Size",
"cardcolor": "Colored Cards",
"cardsettings": "Card Settings",
"clm_no": "Claim Number",
@@ -2828,48 +2799,88 @@
"detailpriority": "D/P",
"employeeassignments": "Employee Assignments",
"employeesearch": "Employee Search",
"estimator": "Estimator",
"horizontal": "Horizontal",
"ins_co_nm": "Insurance Company Name",
"jobdetail": "Job Details",
"kiosk_mode": "Kiosk Mode",
"laborhrs": "Labor Hours",
"legend": "Legend:",
"model_info": "Vehicle Info",
"note": "Production Note",
"off": "Off",
"on": "On",
"orientation": "Board Orientation",
"ownr_nm": "Customer Name",
"paintpriority": "P/P",
"partsstatus": "Parts Status",
"estimator": "Estimator",
"subtotal": "Subtotal",
"production_note": "Production Note",
"refinishhours": "R",
"scheduled_completion": "Scheduled Completion",
"selectview": "Select a View",
"stickyheader": "Sticky Header (BETA)",
"sublets": "Sublets",
"subtotal": "Subtotal",
"tall": "Tall",
"tasks": "Tasks",
"totalhours": "Total Hrs ",
"touchtime": "T/T",
"vertical": "Vertical",
"viewname": "View Name",
"alerts": "Alerts",
"addnewprofile": "Add New Profile"
"wide": "Wide"
},
"options": {
"horizontal": "Horizontal",
"large": "Large",
"medium": "Medium",
"small": "Small",
"vertical": "Vertical"
},
"settings": {
"board_settings": "Board Settings",
"filters": {
"md_estimators": "Estimators",
"md_ins_cos": "Insurance Companies"
},
"filters_title": "Filters",
"information": "Information",
"layout": "Layout",
"statistics": {
"jobs_in_production": "Jobs in Production",
"tasks_in_production": "Tasks in Production",
"tasks_on_board": "Tasks on Board",
"total_amount_in_production": "Dollars in Production",
"total_amount_on_board": "Dollars on Board",
"total_hours_in_production": "Hours in Production",
"total_hours_on_board": "Hours on Board",
"total_jobs_on_board": "Jobs on Board",
"total_lab_in_production": "Body Hours in Production",
"total_lab_on_board": "Body Hours on Board",
"total_lar_in_production": "Refinish Hours in Production",
"total_lar_on_board": "Refinish Hours on Board"
},
"statistics_title": "Statistics"
},
"statistics": {
"currency_symbol": "$",
"hours": "Hours",
"jobs": "Jobs",
"jobs_in_production": "Jobs in Production",
"tasks": "Tasks",
"tasks_in_production": "Tasks in Production",
"tasks_on_board": "Tasks on Board",
"total_amount_in_production": "Dollars in Production",
"total_amount_on_board": "Dollars on Board",
"total_hours_in_production": "Hours in Production",
"total_hours_on_board": "Hours on Board",
"total_jobs_on_board": "Jobs on Board",
"total_lab_in_production": "Body Hours in Production",
"total_lab_on_board": "Body Hours on Board",
"total_lar_in_production": "Refinish Hours in Production",
"total_lar_on_board": "Refinish Hours on Board"
},
"successes": {
"removed": "Job removed from production."
},
"statistics": {
"total_hours_in_production": "Hours in Production",
"total_lab_in_production": "Body Hours in Production",
"total_lar_in_production": "Refinish Hours in Production",
"total_amount_in_production": "Dollars in Production",
"jobs_in_production": "Jobs in Production",
"total_hours_on_board": "Hours on Board",
"total_lab_on_board": "Body Hours on Board",
"total_lar_on_board": "Refinish Hours on Board",
"total_amount_on_board": "Dollars on Board",
"total_jobs_on_board": "Jobs on Board",
"tasks_in_production": "Tasks in Production",
"tasks_on_board": "Tasks on Board",
"tasks": "Tasks",
"hours": "Hours",
"currency_symbol": "$",
"jobs": "Jobs"
}
},
"profile": {
@@ -2927,6 +2938,8 @@
"vendor": "Vendor"
},
"templates": {
"adp_payroll_flat": "ADP Payroll - Flat Rate",
"adp_payroll_straight": "ADP Payroll - Straight Time",
"anticipated_revenue": "Anticipated Revenue",
"ar_aging": "AR Aging",
"attendance_detail": "Attendance (All Employees)",
@@ -3418,6 +3431,18 @@
"vehicledetail": "Vehicle Details {{vehicle}} | {{app}}",
"vehicles": "All Vehicles | {{app}}"
},
"trello": {
"labels": {
"add_card": "Add Card",
"add_lane": "Add Lane",
"cancel": "Cancel",
"delete_lane": "Delete Lane",
"description": "Description",
"label": "Label",
"lane_actions": "Lane Actions",
"title": "Title"
}
},
"tt_approvals": {
"actions": {
"approveselected": "Approve Selected"
@@ -3556,18 +3581,6 @@
"validation": {
"unique_vendor_name": "You must enter a unique vendor name."
}
},
"trello": {
"labels": {
"add_card": "Add Card",
"add_lane": "Add Lane",
"delete_lane": "Delete Lane",
"lane_actions": "Lane Actions",
"title": "Title",
"description": "Description",
"label": "Label",
"cancel": "Cancel"
}
}
}
}

View File

@@ -270,9 +270,9 @@
"testrender": ""
},
"errors": {
"creatingdefaultview": "",
"loading": "No se pueden cargar los detalles de la tienda. Por favor llame al soporte técnico.",
"saving": "",
"creatingdefaultview": ""
"saving": ""
},
"fields": {
"ReceivableCustomField": "",
@@ -285,19 +285,21 @@
},
"appt_length": "",
"attach_pdf_to_email": "",
"batchid": "",
"bill_allow_post_to_closed": "",
"bill_federal_tax_rate": "",
"bill_local_tax_rate": "",
"bill_state_tax_rate": "",
"city": "",
"closingperiod": "",
"companycode": "",
"country": "",
"dailybodytarget": "",
"dailypainttarget": "",
"default_adjustment_rate": "",
"deliver": {
"templates": "",
"require_actual_delivery_date": ""
"require_actual_delivery_date": "",
"templates": ""
},
"dms": {
"apcontrol": "",
@@ -330,6 +332,10 @@
"next_contact_hours": "",
"templates": ""
},
"intellipay_config": {
"cash_discount_percentage": "",
"enable_cash_discount": ""
},
"invoice_federal_tax_rate": "",
"invoice_local_tax_rate": "",
"invoice_state_tax_rate": "",
@@ -661,6 +667,8 @@
"filehandlers": "",
"insurancecos": "",
"intakechecklist": "",
"intellipay": "",
"intellipay_cash_discount": "",
"jobstatuses": "",
"laborrates": "",
"licensing": "",
@@ -700,10 +708,10 @@
"workingdays": ""
},
"successes": {
"save": "",
"unsavedchanges": "",
"areyousure": "",
"defaultviewcreated": ""
"defaultviewcreated": "",
"save": "",
"unsavedchanges": ""
},
"validation": {
"centermustexist": "",
@@ -1133,8 +1141,8 @@
},
"general": {
"actions": {
"defaults": "defaults",
"add": "",
"autoupdate": "",
"calculate": "",
"cancel": "",
"clear": "",
@@ -1142,6 +1150,8 @@
"copied": "",
"copylink": "",
"create": "",
"defaults": "defaults",
"delay": "",
"delete": "Borrar",
"deleteall": "",
"deselectall": "",
@@ -1153,10 +1163,12 @@
"print": "",
"refresh": "",
"remove": "",
"remove_alert": "",
"reset": " Restablecer a original.",
"resetpassword": "",
"save": "Salvar",
"saveandnew": "",
"saveas": "",
"selectall": "",
"send": "",
"sendbysms": "",
@@ -1164,9 +1176,7 @@
"submit": "",
"tryagain": "",
"view": "",
"viewreleasenotes": "",
"remove_alert": "",
"saveas": ""
"viewreleasenotes": ""
},
"errors": {
"fcm": "",
@@ -1181,7 +1191,6 @@
"vehicle": ""
},
"labels": {
"unsavedchanges": "",
"actions": "Comportamiento",
"areyousure": "",
"barcode": "código de barras",
@@ -1250,6 +1259,7 @@
"tuesday": "",
"tvmode": "",
"unknown": "Desconocido",
"unsavedchanges": "",
"username": "",
"view": "",
"wednesday": "",
@@ -1363,6 +1373,7 @@
},
"job_payments": {
"buttons": {
"create_short_link": "",
"goback": "",
"proceedtopayment": "",
"refundpayment": ""
@@ -2739,41 +2750,6 @@
}
},
"production": {
"constants": {
"main_profile": ""
},
"options": {
"small": "",
"medium": "",
"large": "",
"vertical": "",
"horizontal": ""
},
"settings": {
"layout": "",
"information": "",
"statistics_title": "",
"board_settings": "",
"filters_title": "",
"filters": {
"md_ins_cos": "",
"md_estimators": ""
},
"statistics": {
"total_hours_in_production": "",
"total_lab_in_production": "",
"total_lar_in_production": "",
"total_amount_in_production": "",
"jobs_in_production": "",
"total_hours_on_board": "",
"total_lab_on_board": "",
"total_lar_on_board": "",
"total_amount_on_board": "",
"total_jobs_on_board": "",
"tasks_in_production": "",
"tasks_on_board": ""
}
},
"actions": {
"addcolumns": "",
"bodypriority-clear": "",
@@ -2788,29 +2764,23 @@
"suspend": "",
"unsuspend": ""
},
"constants": {
"main_profile": ""
},
"errors": {
"boardupdate": "",
"removing": "",
"settings": "",
"name_exists": "",
"name_required": ""
"name_required": "",
"removing": "",
"settings": ""
},
"labels": {
"kiosk_mode": "",
"on": "",
"off": "",
"wide": "",
"tall": "",
"vertical": "",
"horizontal": "",
"orientation": "",
"card_size": "",
"model_info": "",
"actual_in": "",
"addnewprofile": "",
"alert": "",
"tasks": "",
"alertoff": "",
"alerton": "",
"alerts": "",
"ats": "",
"bodyhours": "",
"bodypriority": "",
@@ -2820,6 +2790,7 @@
"qbo_usa": ""
}
},
"card_size": "",
"cardcolor": "",
"cardsettings": "",
"clm_no": "",
@@ -2828,48 +2799,88 @@
"detailpriority": "",
"employeeassignments": "",
"employeesearch": "",
"estimator": "",
"horizontal": "",
"ins_co_nm": "",
"jobdetail": "",
"kiosk_mode": "",
"laborhrs": "",
"legend": "",
"model_info": "",
"note": "",
"off": "",
"on": "",
"orientation": "",
"ownr_nm": "",
"paintpriority": "",
"partsstatus": "",
"estimator": "",
"subtotal": "",
"production_note": "",
"refinishhours": "",
"scheduled_completion": "",
"selectview": "",
"stickyheader": "",
"sublets": "",
"subtotal": "",
"tall": "",
"tasks": "",
"totalhours": "",
"touchtime": "",
"vertical": "",
"viewname": "",
"alerts": "",
"addnewprofile": ""
"wide": ""
},
"options": {
"horizontal": "",
"large": "",
"medium": "",
"small": "",
"vertical": ""
},
"settings": {
"board_settings": "",
"filters": {
"md_estimators": "",
"md_ins_cos": ""
},
"filters_title": "",
"information": "",
"layout": "",
"statistics": {
"jobs_in_production": "",
"tasks_in_production": "",
"tasks_on_board": "",
"total_amount_in_production": "",
"total_amount_on_board": "",
"total_hours_in_production": "",
"total_hours_on_board": "",
"total_jobs_on_board": "",
"total_lab_in_production": "",
"total_lab_on_board": "",
"total_lar_in_production": "",
"total_lar_on_board": ""
},
"statistics_title": ""
},
"statistics": {
"currency_symbol": "",
"hours": "",
"jobs": "",
"jobs_in_production": "",
"tasks": "",
"tasks_in_production": "",
"tasks_on_board": "",
"total_amount_in_production": "",
"total_amount_on_board": "",
"total_hours_in_production": "",
"total_hours_on_board": "",
"total_jobs_on_board": "",
"total_lab_in_production": "",
"total_lab_on_board": "",
"total_lar_in_production": "",
"total_lar_on_board": ""
},
"successes": {
"removed": ""
},
"statistics": {
"total_hours_in_production": "",
"total_lab_in_production": "",
"total_lar_in_production": "",
"total_amount_in_production": "",
"jobs_in_production": "",
"total_hours_on_board": "",
"total_lab_on_board": "",
"total_lar_on_board": "",
"total_amount_on_board": "",
"total_jobs_on_board": "",
"tasks_in_production": "",
"tasks_on_board": "",
"tasks": "",
"hours": "",
"currency_symbol": "",
"jobs": ""
}
},
"profile": {
@@ -2927,6 +2938,8 @@
"vendor": ""
},
"templates": {
"adp_payroll_flat": "",
"adp_payroll_straight": "",
"anticipated_revenue": "",
"ar_aging": "",
"attendance_detail": "",
@@ -3418,6 +3431,18 @@
"vehicledetail": "Detalles del vehículo {{vehicle}} | {{app}}",
"vehicles": "Todos los vehiculos | {{app}}"
},
"trello": {
"labels": {
"add_card": "",
"add_lane": "",
"cancel": "",
"delete_lane": "",
"description": "",
"label": "",
"lane_actions": "",
"title": ""
}
},
"tt_approvals": {
"actions": {
"approveselected": ""
@@ -3556,18 +3581,6 @@
"validation": {
"unique_vendor_name": ""
}
},
"trello": {
"labels": {
"add_card": "",
"add_lane": "",
"delete_lane": "",
"lane_actions": "",
"title": "",
"description": "",
"label": "",
"cancel": ""
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2158,6 +2158,32 @@ export const TemplateList = (type, context) => {
field: i18n.t("tasks.fields.created_at")
},
group: "jobs"
},
adp_payroll_flat: {
title: i18n.t("reportcenter.templates.adp_payroll_flat"),
subject: i18n.t("reportcenter.templates.adp_payroll_flat"),
key: "adp_payroll_flat",
reporttype: "text",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.timetickets"),
field: i18n.t("timetickets.fields.committed_at")
},
group: "payroll",
adp_payroll: true
},
adp_payroll_straight: {
title: i18n.t("reportcenter.templates.adp_payroll_straight"),
subject: i18n.t("reportcenter.templates.adp_payroll_straight"),
key: "adp_payroll_straight",
reporttype: "text",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.timetickets"),
field: i18n.t("timetickets.fields.date")
},
group: "payroll",
adp_payroll: true
}
}
: {}),

View File

@@ -0,0 +1,84 @@
import React from "react";
const useCountDown = (timeToCount = 60 * 1000, interval = 1000) => {
const [timeLeft, setTimeLeft] = React.useState(0);
const timer = React.useRef({});
const run = (ts) => {
if (!timer.current.started) {
timer.current.started = ts;
timer.current.lastInterval = ts;
}
const localInterval = Math.min(interval, timer.current.timeLeft || Infinity);
if (ts - timer.current.lastInterval >= localInterval) {
timer.current.lastInterval += localInterval;
setTimeLeft((timeLeft) => {
timer.current.timeLeft = timeLeft - localInterval;
return timer.current.timeLeft;
});
}
if (ts - timer.current.started < timer.current.timeToCount) {
timer.current.requestId = window.requestAnimationFrame(run);
} else {
timer.current = {};
setTimeLeft(0);
}
};
const start = React.useCallback(
(ttc) => {
window.cancelAnimationFrame(timer.current.requestId);
const newTimeToCount = ttc !== undefined ? ttc : timeToCount;
timer.current.started = null;
timer.current.lastInterval = null;
timer.current.timeToCount = newTimeToCount;
timer.current.requestId = window.requestAnimationFrame(run);
setTimeLeft(newTimeToCount);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
const pause = React.useCallback(() => {
window.cancelAnimationFrame(timer.current.requestId);
timer.current.started = null;
timer.current.lastInterval = null;
timer.current.timeToCount = timer.current.timeLeft;
}, []);
const resume = React.useCallback(
() => {
if (!timer.current.started && timer.current.timeLeft > 0) {
window.cancelAnimationFrame(timer.current.requestId);
timer.current.requestId = window.requestAnimationFrame(run);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
const reset = React.useCallback(() => {
if (timer.current.timeLeft) {
window.cancelAnimationFrame(timer.current.requestId);
timer.current = {};
setTimeLeft(0);
}
}, []);
const actions = React.useMemo(
() => ({ start, pause, resume, reset }), // eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
React.useEffect(() => {
return () => window.cancelAnimationFrame(timer.current.requestId);
}, []);
return [timeLeft, actions];
};
export default useCountDown;

View File

@@ -1,47 +0,0 @@
export const BETA_KEY = "betaSwitchImex";
export const checkBeta = () => {
const cookie = document.cookie.split("; ").find((row) => row.startsWith(BETA_KEY));
return cookie ? cookie.split("=")[1] === "true" : false;
};
export const setBeta = (value) => {
const domain = window.location.hostname.split(".").slice(-2).join(".");
document.cookie = `${BETA_KEY}=${value}; path=/; domain=.${domain}`;
};
export const handleBeta = () => {
if (window.location.hostname.startsWith("localhost")) {
console.log("Not on beta or test, so no need to handle beta.");
return;
}
const isBeta = checkBeta();
const currentHostName = window.location.hostname;
// Determine if the host name starts with "beta" or "www.beta"
const isBetaHost = currentHostName.startsWith("beta.");
const isBetaHostWithWWW = currentHostName.startsWith("www.beta.");
if (isBeta) {
// If beta is on and we are not on a beta domain, redirect to the beta version
if (!isBetaHost && !isBetaHostWithWWW) {
const newHostName = currentHostName.startsWith("www.")
? `www.beta.${currentHostName.replace(/^www\./, "")}`
: `beta.${currentHostName}`;
const href = `${window.location.protocol}//${newHostName}${window.location.pathname}${window.location.search}${window.location.hash}`;
window.location.replace(href);
}
// Otherwise, if beta is on and we're already on a beta domain, stay there
} else {
// If beta is off and we are on a beta domain, redirect to the non-beta version
if (isBetaHost || isBetaHostWithWWW) {
const newHostName = currentHostName.replace(/^www\.beta\./, "www.").replace(/^beta\./, "");
const href = `${window.location.protocol}//${newHostName}${window.location.pathname}${window.location.search}${window.location.hash}`;
window.location.replace(href);
}
// Otherwise, if beta is off and we're not on a beta domain, stay there
}
};
export default handleBeta;

View File

@@ -121,7 +121,7 @@ export default defineConfig({
}
},
optimizeDeps: {
include: ["react", "react-dom", "antd", "@apollo/client", "@reduxjs/toolkit", "axios", "react-grid-layout"],
include: ["react", "react-dom", "antd", "@apollo/client", "@reduxjs/toolkit", "axios"],
esbuildOptions: {
loader: {
".js": "jsx"

View File

@@ -1,5 +1,5 @@
version: 2
endpoint: https://db.dev.bodyshop.app
endpoint: https://db.dev.imex.online
admin_secret: Dev-BodyShopApp!
metadata_directory: metadata
actions:

View File

@@ -939,6 +939,7 @@
- inhousevendorid
- insurance_vendor_id
- intakechecklist
- intellipay_config
- jc_hourly_rates
- jobsizelimit
- last_name_first
@@ -1040,6 +1041,7 @@
- inhousevendorid
- insurance_vendor_id
- intakechecklist
- intellipay_config
- jc_hourly_rates
- last_name_first
- localmediaserverhttp
@@ -4240,6 +4242,63 @@
- active:
_eq: true
event_triggers:
- name: job_modified
definition:
enable_manual: false
update:
columns:
- clm_no
- v_make_desc
- date_next_contact
- status
- employee_csr
- employee_prep
- clm_total
- suspended
- employee_body
- ro_number
- actual_in
- ownr_co_nm
- v_model_yr
- comment
- job_totals
- v_vin
- ownr_fn
- scheduled_completion
- special_coverage_policy
- v_color
- ca_gst_registrant
- scheduled_delivery
- actual_delivery
- actual_completion
- kanbanparent
- est_ct_fn
- employee_refinish
- ownr_ph1
- date_last_contacted
- alt_transport
- inproduction
- est_ct_ln
- production_vars
- category
- v_model_desc
- date_invoiced
- est_co_nm
- ownr_ln
retry_conf:
interval_sec: 10
num_retries: 0
timeout_sec: 60
webhook_from_env: HASURA_API_URL
headers:
- name: event-secret
value_from_env: EVENT_SECRET
request_transform:
method: POST
query_params: {}
template_engine: Kriti
url: '{{$base_url}}/job/job-updated'
version: 2
- name: job_status_transition
definition:
enable_manual: true

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."bodyshops" add column "intellipay_config" jsonb
-- not null default jsonb_build_object();

View File

@@ -0,0 +1,2 @@
alter table "public"."bodyshops" add column "intellipay_config" jsonb
not null default jsonb_build_object();

341
package-lock.json generated
View File

@@ -9,17 +9,17 @@
"version": "0.2.0",
"license": "UNLICENSED",
"dependencies": {
"@aws-sdk/client-secrets-manager": "^3.637.0",
"@aws-sdk/client-ses": "^3.637.0",
"@aws-sdk/credential-provider-node": "^3.637.0",
"@opensearch-project/opensearch": "^2.12.0",
"aws4": "^1.13.2",
"axios": "^1.7.7",
"@aws-sdk/client-secrets-manager": "^3.629.0",
"@aws-sdk/client-ses": "^3.629.0",
"@aws-sdk/credential-provider-node": "^3.629.0",
"@opensearch-project/opensearch": "^2.11.0",
"aws4": "^1.13.1",
"axios": "^1.7.4",
"better-queue": "^3.8.12",
"bluebird": "^3.7.2",
"body-parser": "^1.20.2",
"canvas": "^2.11.2",
"chart.js": "^4.4.4",
"chart.js": "^4.4.3",
"cloudinary": "^2.4.0",
"compression": "^1.7.4",
"cookie-parser": "^1.4.6",
@@ -28,7 +28,7 @@
"dinero.js": "^1.9.1",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"firebase-admin": "^12.4.0",
"firebase-admin": "^12.3.1",
"graphql": "^16.9.0",
"graphql-request": "^6.1.0",
"graylog2": "^0.2.1",
@@ -39,13 +39,13 @@
"moment": "^2.30.1",
"moment-timezone": "^0.5.45",
"multer": "^1.4.5-lts.1",
"node-mailjet": "^6.0.6",
"node-mailjet": "^6.0.5",
"node-persist": "^4.0.3",
"nodemailer": "^6.9.15",
"phone": "^3.1.50",
"nodemailer": "^6.9.14",
"phone": "^3.1.49",
"recursive-diff": "^1.0.9",
"rimraf": "^6.0.1",
"soap": "^1.1.3",
"soap": "^1.1.1",
"socket.io": "^4.7.5",
"ssh2-sftp-client": "^10.0.3",
"twilio": "^4.23.0",
@@ -180,47 +180,47 @@
}
},
"node_modules/@aws-sdk/client-secrets-manager": {
"version": "3.637.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.637.0.tgz",
"integrity": "sha512-4AEV+4yhaFYlnD90MbtOouqTyrPVmD8OeGotsjtWxgnVHk55Vd0/dIWVGjic0YCxH3SNdWqJJ9G8Vd93fWymVA==",
"version": "3.629.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.629.0.tgz",
"integrity": "sha512-ZZKI9uTQ3WIdbCZK6sveccalLTWgyOZeebi+Gnwl5ztKMk4OfwZKxyhry1DXB8gMrlISniREDb3ZxUZdFNwqfQ==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
"@aws-sdk/client-sso-oidc": "3.637.0",
"@aws-sdk/client-sts": "3.637.0",
"@aws-sdk/core": "3.635.0",
"@aws-sdk/credential-provider-node": "3.637.0",
"@aws-sdk/client-sso-oidc": "3.629.0",
"@aws-sdk/client-sts": "3.629.0",
"@aws-sdk/core": "3.629.0",
"@aws-sdk/credential-provider-node": "3.629.0",
"@aws-sdk/middleware-host-header": "3.620.0",
"@aws-sdk/middleware-logger": "3.609.0",
"@aws-sdk/middleware-recursion-detection": "3.620.0",
"@aws-sdk/middleware-user-agent": "3.637.0",
"@aws-sdk/middleware-user-agent": "3.620.0",
"@aws-sdk/region-config-resolver": "3.614.0",
"@aws-sdk/types": "3.609.0",
"@aws-sdk/util-endpoints": "3.637.0",
"@aws-sdk/util-endpoints": "3.614.0",
"@aws-sdk/util-user-agent-browser": "3.609.0",
"@aws-sdk/util-user-agent-node": "3.614.0",
"@smithy/config-resolver": "^3.0.5",
"@smithy/core": "^2.4.0",
"@smithy/core": "^2.3.2",
"@smithy/fetch-http-handler": "^3.2.4",
"@smithy/hash-node": "^3.0.3",
"@smithy/invalid-dependency": "^3.0.3",
"@smithy/middleware-content-length": "^3.0.5",
"@smithy/middleware-endpoint": "^3.1.0",
"@smithy/middleware-retry": "^3.0.15",
"@smithy/middleware-retry": "^3.0.14",
"@smithy/middleware-serde": "^3.0.3",
"@smithy/middleware-stack": "^3.0.3",
"@smithy/node-config-provider": "^3.1.4",
"@smithy/node-http-handler": "^3.1.4",
"@smithy/protocol-http": "^4.1.0",
"@smithy/smithy-client": "^3.2.0",
"@smithy/smithy-client": "^3.1.12",
"@smithy/types": "^3.3.0",
"@smithy/url-parser": "^3.0.3",
"@smithy/util-base64": "^3.0.0",
"@smithy/util-body-length-browser": "^3.0.0",
"@smithy/util-body-length-node": "^3.0.0",
"@smithy/util-defaults-mode-browser": "^3.0.15",
"@smithy/util-defaults-mode-node": "^3.0.15",
"@smithy/util-defaults-mode-browser": "^3.0.14",
"@smithy/util-defaults-mode-node": "^3.0.14",
"@smithy/util-endpoints": "^2.0.5",
"@smithy/util-middleware": "^3.0.3",
"@smithy/util-retry": "^3.0.3",
@@ -245,47 +245,47 @@
}
},
"node_modules/@aws-sdk/client-ses": {
"version": "3.637.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.637.0.tgz",
"integrity": "sha512-pHkI8iMxbdtNeaa0a+c0T29bRZMFu8HQtGtwPlSwvD1elDVFrlWXw74MrlaW0aRPGryF4sHxVHk3YkxhxAS5iw==",
"version": "3.629.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.629.0.tgz",
"integrity": "sha512-KreCdUAO/gIzWCgnPV1/dGUvLDDTdXI3fZzjjHUWFa1bE4wENjenNnWGw0qZgc8xB8pgiMdgPn7N+JvxJ7c/ZQ==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
"@aws-sdk/client-sso-oidc": "3.637.0",
"@aws-sdk/client-sts": "3.637.0",
"@aws-sdk/core": "3.635.0",
"@aws-sdk/credential-provider-node": "3.637.0",
"@aws-sdk/client-sso-oidc": "3.629.0",
"@aws-sdk/client-sts": "3.629.0",
"@aws-sdk/core": "3.629.0",
"@aws-sdk/credential-provider-node": "3.629.0",
"@aws-sdk/middleware-host-header": "3.620.0",
"@aws-sdk/middleware-logger": "3.609.0",
"@aws-sdk/middleware-recursion-detection": "3.620.0",
"@aws-sdk/middleware-user-agent": "3.637.0",
"@aws-sdk/middleware-user-agent": "3.620.0",
"@aws-sdk/region-config-resolver": "3.614.0",
"@aws-sdk/types": "3.609.0",
"@aws-sdk/util-endpoints": "3.637.0",
"@aws-sdk/util-endpoints": "3.614.0",
"@aws-sdk/util-user-agent-browser": "3.609.0",
"@aws-sdk/util-user-agent-node": "3.614.0",
"@smithy/config-resolver": "^3.0.5",
"@smithy/core": "^2.4.0",
"@smithy/core": "^2.3.2",
"@smithy/fetch-http-handler": "^3.2.4",
"@smithy/hash-node": "^3.0.3",
"@smithy/invalid-dependency": "^3.0.3",
"@smithy/middleware-content-length": "^3.0.5",
"@smithy/middleware-endpoint": "^3.1.0",
"@smithy/middleware-retry": "^3.0.15",
"@smithy/middleware-retry": "^3.0.14",
"@smithy/middleware-serde": "^3.0.3",
"@smithy/middleware-stack": "^3.0.3",
"@smithy/node-config-provider": "^3.1.4",
"@smithy/node-http-handler": "^3.1.4",
"@smithy/protocol-http": "^4.1.0",
"@smithy/smithy-client": "^3.2.0",
"@smithy/smithy-client": "^3.1.12",
"@smithy/types": "^3.3.0",
"@smithy/url-parser": "^3.0.3",
"@smithy/util-base64": "^3.0.0",
"@smithy/util-body-length-browser": "^3.0.0",
"@smithy/util-body-length-node": "^3.0.0",
"@smithy/util-defaults-mode-browser": "^3.0.15",
"@smithy/util-defaults-mode-node": "^3.0.15",
"@smithy/util-defaults-mode-browser": "^3.0.14",
"@smithy/util-defaults-mode-node": "^3.0.14",
"@smithy/util-endpoints": "^2.0.5",
"@smithy/util-middleware": "^3.0.3",
"@smithy/util-retry": "^3.0.3",
@@ -298,44 +298,44 @@
}
},
"node_modules/@aws-sdk/client-sso": {
"version": "3.637.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.637.0.tgz",
"integrity": "sha512-+KjLvgX5yJYROWo3TQuwBJlHCY0zz9PsLuEolmXQn0BVK1L/m9GteZHtd+rEdAoDGBpE0Xqjy1oz5+SmtsaRUw==",
"version": "3.629.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.629.0.tgz",
"integrity": "sha512-2w8xU4O0Grca5HmT2dXZ5fF0g39RxODtmoqHJDsK5DSt750LqDG4w3ktmBvQs3+SrpkkJOjlX5v/hb2PCxVbww==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
"@aws-sdk/core": "3.635.0",
"@aws-sdk/core": "3.629.0",
"@aws-sdk/middleware-host-header": "3.620.0",
"@aws-sdk/middleware-logger": "3.609.0",
"@aws-sdk/middleware-recursion-detection": "3.620.0",
"@aws-sdk/middleware-user-agent": "3.637.0",
"@aws-sdk/middleware-user-agent": "3.620.0",
"@aws-sdk/region-config-resolver": "3.614.0",
"@aws-sdk/types": "3.609.0",
"@aws-sdk/util-endpoints": "3.637.0",
"@aws-sdk/util-endpoints": "3.614.0",
"@aws-sdk/util-user-agent-browser": "3.609.0",
"@aws-sdk/util-user-agent-node": "3.614.0",
"@smithy/config-resolver": "^3.0.5",
"@smithy/core": "^2.4.0",
"@smithy/core": "^2.3.2",
"@smithy/fetch-http-handler": "^3.2.4",
"@smithy/hash-node": "^3.0.3",
"@smithy/invalid-dependency": "^3.0.3",
"@smithy/middleware-content-length": "^3.0.5",
"@smithy/middleware-endpoint": "^3.1.0",
"@smithy/middleware-retry": "^3.0.15",
"@smithy/middleware-retry": "^3.0.14",
"@smithy/middleware-serde": "^3.0.3",
"@smithy/middleware-stack": "^3.0.3",
"@smithy/node-config-provider": "^3.1.4",
"@smithy/node-http-handler": "^3.1.4",
"@smithy/protocol-http": "^4.1.0",
"@smithy/smithy-client": "^3.2.0",
"@smithy/smithy-client": "^3.1.12",
"@smithy/types": "^3.3.0",
"@smithy/url-parser": "^3.0.3",
"@smithy/util-base64": "^3.0.0",
"@smithy/util-body-length-browser": "^3.0.0",
"@smithy/util-body-length-node": "^3.0.0",
"@smithy/util-defaults-mode-browser": "^3.0.15",
"@smithy/util-defaults-mode-node": "^3.0.15",
"@smithy/util-defaults-mode-browser": "^3.0.14",
"@smithy/util-defaults-mode-node": "^3.0.14",
"@smithy/util-endpoints": "^2.0.5",
"@smithy/util-middleware": "^3.0.3",
"@smithy/util-retry": "^3.0.3",
@@ -347,45 +347,45 @@
}
},
"node_modules/@aws-sdk/client-sso-oidc": {
"version": "3.637.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.637.0.tgz",
"integrity": "sha512-27bHALN6Qb6m6KZmPvRieJ/QRlj1lyac/GT2Rn5kJpre8Mpp+yxrtvp3h9PjNBty4lCeFEENfY4dGNSozBuBcw==",
"version": "3.629.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.629.0.tgz",
"integrity": "sha512-3if0LauNJPqubGYf8vnlkp+B3yAeKRuRNxfNbHlE6l510xWGcKK/ZsEmiFmfePzKKSRrDh/cxMFMScgOrXptNg==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
"@aws-sdk/core": "3.635.0",
"@aws-sdk/credential-provider-node": "3.637.0",
"@aws-sdk/core": "3.629.0",
"@aws-sdk/credential-provider-node": "3.629.0",
"@aws-sdk/middleware-host-header": "3.620.0",
"@aws-sdk/middleware-logger": "3.609.0",
"@aws-sdk/middleware-recursion-detection": "3.620.0",
"@aws-sdk/middleware-user-agent": "3.637.0",
"@aws-sdk/middleware-user-agent": "3.620.0",
"@aws-sdk/region-config-resolver": "3.614.0",
"@aws-sdk/types": "3.609.0",
"@aws-sdk/util-endpoints": "3.637.0",
"@aws-sdk/util-endpoints": "3.614.0",
"@aws-sdk/util-user-agent-browser": "3.609.0",
"@aws-sdk/util-user-agent-node": "3.614.0",
"@smithy/config-resolver": "^3.0.5",
"@smithy/core": "^2.4.0",
"@smithy/core": "^2.3.2",
"@smithy/fetch-http-handler": "^3.2.4",
"@smithy/hash-node": "^3.0.3",
"@smithy/invalid-dependency": "^3.0.3",
"@smithy/middleware-content-length": "^3.0.5",
"@smithy/middleware-endpoint": "^3.1.0",
"@smithy/middleware-retry": "^3.0.15",
"@smithy/middleware-retry": "^3.0.14",
"@smithy/middleware-serde": "^3.0.3",
"@smithy/middleware-stack": "^3.0.3",
"@smithy/node-config-provider": "^3.1.4",
"@smithy/node-http-handler": "^3.1.4",
"@smithy/protocol-http": "^4.1.0",
"@smithy/smithy-client": "^3.2.0",
"@smithy/smithy-client": "^3.1.12",
"@smithy/types": "^3.3.0",
"@smithy/url-parser": "^3.0.3",
"@smithy/util-base64": "^3.0.0",
"@smithy/util-body-length-browser": "^3.0.0",
"@smithy/util-body-length-node": "^3.0.0",
"@smithy/util-defaults-mode-browser": "^3.0.15",
"@smithy/util-defaults-mode-node": "^3.0.15",
"@smithy/util-defaults-mode-browser": "^3.0.14",
"@smithy/util-defaults-mode-node": "^3.0.14",
"@smithy/util-endpoints": "^2.0.5",
"@smithy/util-middleware": "^3.0.3",
"@smithy/util-retry": "^3.0.3",
@@ -396,50 +396,50 @@
"node": ">=16.0.0"
},
"peerDependencies": {
"@aws-sdk/client-sts": "^3.637.0"
"@aws-sdk/client-sts": "^3.629.0"
}
},
"node_modules/@aws-sdk/client-sts": {
"version": "3.637.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.637.0.tgz",
"integrity": "sha512-xUi7x4qDubtA8QREtlblPuAcn91GS/09YVEY/RwU7xCY0aqGuFwgszAANlha4OUIqva8oVj2WO4gJuG+iaSnhw==",
"version": "3.629.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.629.0.tgz",
"integrity": "sha512-RjOs371YwnSVGxhPjuluJKaxl4gcPYTAky0nPjwBime0i9/iS9nI8R8l5j7k7ec9tpFWjBPvNnThCU07pvjdzw==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
"@aws-sdk/client-sso-oidc": "3.637.0",
"@aws-sdk/core": "3.635.0",
"@aws-sdk/credential-provider-node": "3.637.0",
"@aws-sdk/client-sso-oidc": "3.629.0",
"@aws-sdk/core": "3.629.0",
"@aws-sdk/credential-provider-node": "3.629.0",
"@aws-sdk/middleware-host-header": "3.620.0",
"@aws-sdk/middleware-logger": "3.609.0",
"@aws-sdk/middleware-recursion-detection": "3.620.0",
"@aws-sdk/middleware-user-agent": "3.637.0",
"@aws-sdk/middleware-user-agent": "3.620.0",
"@aws-sdk/region-config-resolver": "3.614.0",
"@aws-sdk/types": "3.609.0",
"@aws-sdk/util-endpoints": "3.637.0",
"@aws-sdk/util-endpoints": "3.614.0",
"@aws-sdk/util-user-agent-browser": "3.609.0",
"@aws-sdk/util-user-agent-node": "3.614.0",
"@smithy/config-resolver": "^3.0.5",
"@smithy/core": "^2.4.0",
"@smithy/core": "^2.3.2",
"@smithy/fetch-http-handler": "^3.2.4",
"@smithy/hash-node": "^3.0.3",
"@smithy/invalid-dependency": "^3.0.3",
"@smithy/middleware-content-length": "^3.0.5",
"@smithy/middleware-endpoint": "^3.1.0",
"@smithy/middleware-retry": "^3.0.15",
"@smithy/middleware-retry": "^3.0.14",
"@smithy/middleware-serde": "^3.0.3",
"@smithy/middleware-stack": "^3.0.3",
"@smithy/node-config-provider": "^3.1.4",
"@smithy/node-http-handler": "^3.1.4",
"@smithy/protocol-http": "^4.1.0",
"@smithy/smithy-client": "^3.2.0",
"@smithy/smithy-client": "^3.1.12",
"@smithy/types": "^3.3.0",
"@smithy/url-parser": "^3.0.3",
"@smithy/util-base64": "^3.0.0",
"@smithy/util-body-length-browser": "^3.0.0",
"@smithy/util-body-length-node": "^3.0.0",
"@smithy/util-defaults-mode-browser": "^3.0.15",
"@smithy/util-defaults-mode-node": "^3.0.15",
"@smithy/util-defaults-mode-browser": "^3.0.14",
"@smithy/util-defaults-mode-node": "^3.0.14",
"@smithy/util-endpoints": "^2.0.5",
"@smithy/util-middleware": "^3.0.3",
"@smithy/util-retry": "^3.0.3",
@@ -451,17 +451,17 @@
}
},
"node_modules/@aws-sdk/core": {
"version": "3.635.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.635.0.tgz",
"integrity": "sha512-i1x/E/sgA+liUE1XJ7rj1dhyXpAKO1UKFUcTTHXok2ARjWTvszHnSXMOsB77aPbmn0fUp1JTx2kHUAZ1LVt5Bg==",
"version": "3.629.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.629.0.tgz",
"integrity": "sha512-+/ShPU/tyIBM3oY1cnjgNA/tFyHtlWq+wXF9xEKRv19NOpYbWQ+xzNwVjGq8vR07cCRqy/sDQLWPhxjtuV/FiQ==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/core": "^2.4.0",
"@smithy/core": "^2.3.2",
"@smithy/node-config-provider": "^3.1.4",
"@smithy/property-provider": "^3.1.3",
"@smithy/protocol-http": "^4.1.0",
"@smithy/signature-v4": "^4.1.0",
"@smithy/smithy-client": "^3.2.0",
"@smithy/smithy-client": "^3.1.12",
"@smithy/types": "^3.3.0",
"@smithy/util-middleware": "^3.0.3",
"fast-xml-parser": "4.4.1",
@@ -487,9 +487,9 @@
}
},
"node_modules/@aws-sdk/credential-provider-http": {
"version": "3.635.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.635.0.tgz",
"integrity": "sha512-iJyRgEjOCQlBMXqtwPLIKYc7Bsc6nqjrZybdMDenPDa+kmLg7xh8LxHsu9088e+2/wtLicE34FsJJIfzu3L82g==",
"version": "3.622.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.622.0.tgz",
"integrity": "sha512-VUHbr24Oll1RK3WR8XLUugLpgK9ZuxEm/NVeVqyFts1Ck9gsKpRg1x4eH7L7tW3SJ4TDEQNMbD7/7J+eoL2svg==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/types": "3.609.0",
@@ -497,7 +497,7 @@
"@smithy/node-http-handler": "^3.1.4",
"@smithy/property-provider": "^3.1.3",
"@smithy/protocol-http": "^4.1.0",
"@smithy/smithy-client": "^3.2.0",
"@smithy/smithy-client": "^3.1.12",
"@smithy/types": "^3.3.0",
"@smithy/util-stream": "^3.1.3",
"tslib": "^2.6.2"
@@ -507,15 +507,15 @@
}
},
"node_modules/@aws-sdk/credential-provider-ini": {
"version": "3.637.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.637.0.tgz",
"integrity": "sha512-h+PFCWfZ0Q3Dx84SppET/TFpcQHmxFW8/oV9ArEvMilw4EBN+IlxgbL0CnHwjHW64szcmrM0mbebjEfHf4FXmw==",
"version": "3.629.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.629.0.tgz",
"integrity": "sha512-r9fI7BABARvVDp77DBUImQzYdvarAIdhbvpCEZib0rlpvfWu3zxE9KZcapCAAi0MPjxeDfb7RMehFQIkAP7mYw==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/credential-provider-env": "3.620.1",
"@aws-sdk/credential-provider-http": "3.635.0",
"@aws-sdk/credential-provider-http": "3.622.0",
"@aws-sdk/credential-provider-process": "3.620.1",
"@aws-sdk/credential-provider-sso": "3.637.0",
"@aws-sdk/credential-provider-sso": "3.629.0",
"@aws-sdk/credential-provider-web-identity": "3.621.0",
"@aws-sdk/types": "3.609.0",
"@smithy/credential-provider-imds": "^3.2.0",
@@ -528,20 +528,20 @@
"node": ">=16.0.0"
},
"peerDependencies": {
"@aws-sdk/client-sts": "^3.637.0"
"@aws-sdk/client-sts": "^3.629.0"
}
},
"node_modules/@aws-sdk/credential-provider-node": {
"version": "3.637.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.637.0.tgz",
"integrity": "sha512-yoEhoxJJfs7sPVQ6Is939BDQJZpZCoUgKr/ySse4YKOZ24t4VqgHA6+wV7rYh+7IW24Rd91UTvEzSuHYTlxlNA==",
"version": "3.629.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.629.0.tgz",
"integrity": "sha512-868hnVOLlXOBHk91Rl0jZIRgr/M4WJCa0nOrW9A9yidsQxuZp9P0vshDmm4hMvNZadmPIfo0Rra2MpA4RELoCw==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/credential-provider-env": "3.620.1",
"@aws-sdk/credential-provider-http": "3.635.0",
"@aws-sdk/credential-provider-ini": "3.637.0",
"@aws-sdk/credential-provider-http": "3.622.0",
"@aws-sdk/credential-provider-ini": "3.629.0",
"@aws-sdk/credential-provider-process": "3.620.1",
"@aws-sdk/credential-provider-sso": "3.637.0",
"@aws-sdk/credential-provider-sso": "3.629.0",
"@aws-sdk/credential-provider-web-identity": "3.621.0",
"@aws-sdk/types": "3.609.0",
"@smithy/credential-provider-imds": "^3.2.0",
@@ -571,12 +571,12 @@
}
},
"node_modules/@aws-sdk/credential-provider-sso": {
"version": "3.637.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.637.0.tgz",
"integrity": "sha512-Mvz+h+e62/tl+dVikLafhv+qkZJ9RUb8l2YN/LeKMWkxQylPT83CPk9aimVhCV89zth1zpREArl97+3xsfgQvA==",
"version": "3.629.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.629.0.tgz",
"integrity": "sha512-Lf4XOuj6jamxgGZGrVojERh5S+NS2t2S4CUOnAu6tJ5U0GPlpjhINUKlcVxJBpsIXudMGW1nkumAd3+kazCPig==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/client-sso": "3.637.0",
"@aws-sdk/client-sso": "3.629.0",
"@aws-sdk/token-providers": "3.614.0",
"@aws-sdk/types": "3.609.0",
"@smithy/property-provider": "^3.1.3",
@@ -650,13 +650,13 @@
}
},
"node_modules/@aws-sdk/middleware-user-agent": {
"version": "3.637.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.637.0.tgz",
"integrity": "sha512-EYo0NE9/da/OY8STDsK2LvM4kNa79DBsf4YVtaG4P5pZ615IeFsD8xOHZeuJmUrSMlVQ8ywPRX7WMucUybsKug==",
"version": "3.620.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.620.0.tgz",
"integrity": "sha512-bvS6etn+KsuL32ubY5D3xNof1qkenpbJXf/ugGXbg0n98DvDFQ/F+SMLxHgbnER5dsKYchNnhmtI6/FC3HFu/A==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/types": "3.609.0",
"@aws-sdk/util-endpoints": "3.637.0",
"@aws-sdk/util-endpoints": "3.614.0",
"@smithy/protocol-http": "^4.1.0",
"@smithy/types": "^3.3.0",
"tslib": "^2.6.2"
@@ -713,10 +713,9 @@
}
},
"node_modules/@aws-sdk/util-endpoints": {
"version": "3.637.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.637.0.tgz",
"integrity": "sha512-pAqOKUHeVWHEXXDIp/qoMk/6jyxIb6GGjnK1/f8dKHtKIEs4tKsnnL563gceEvdad53OPXIt86uoevCcCzmBnw==",
"license": "Apache-2.0",
"version": "3.614.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.614.0.tgz",
"integrity": "sha512-wK2cdrXHH4oz4IomV/yrGkftU9A+ITB6nFL+rxxyO78is2ifHJpFdV4aqk4LSkXYPi6CXWNru/Dqc7yiKXgJPw==",
"dependencies": {
"@aws-sdk/types": "3.609.0",
"@smithy/types": "^3.3.0",
@@ -1664,9 +1663,9 @@
}
},
"node_modules/@opensearch-project/opensearch": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/@opensearch-project/opensearch/-/opensearch-2.12.0.tgz",
"integrity": "sha512-FNGWbWjvpWIZHVvAbv0FkSgvc1PnWnYEHnOTeIY08vMDp9QpXumGNDjNc1tZthJ3OEeoooqH0miGFORjWnRYsQ==",
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/@opensearch-project/opensearch/-/opensearch-2.11.0.tgz",
"integrity": "sha512-G+SZwtWRDv90IrtTSNnCt0MQjHVyqrcIXcpwN68vjHnfbun2+RHn+ux4K7dnG+s/KwWzVKIpPFoRjg2gfFX0Mw==",
"license": "Apache-2.0",
"dependencies": {
"aws4": "^1.11.0",
@@ -1782,20 +1781,18 @@
}
},
"node_modules/@smithy/core": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.4.0.tgz",
"integrity": "sha512-cHXq+FneIF/KJbt4q4pjN186+Jf4ZB0ZOqEaZMBhT79srEyGDDBV31NqBRBjazz8ppQ1bJbDJMY9ba5wKFV36w==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.3.2.tgz",
"integrity": "sha512-in5wwt6chDBcUv1Lw1+QzZxN9fBffi+qOixfb65yK4sDuKG7zAUO9HAFqmVzsZM3N+3tTyvZjtnDXePpvp007Q==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/middleware-endpoint": "^3.1.0",
"@smithy/middleware-retry": "^3.0.15",
"@smithy/middleware-retry": "^3.0.14",
"@smithy/middleware-serde": "^3.0.3",
"@smithy/protocol-http": "^4.1.0",
"@smithy/smithy-client": "^3.2.0",
"@smithy/smithy-client": "^3.1.12",
"@smithy/types": "^3.3.0",
"@smithy/util-body-length-browser": "^3.0.0",
"@smithy/util-middleware": "^3.0.3",
"@smithy/util-utf8": "^3.0.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -1898,15 +1895,15 @@
}
},
"node_modules/@smithy/middleware-retry": {
"version": "3.0.15",
"resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.15.tgz",
"integrity": "sha512-iTMedvNt1ApdvkaoE8aSDuwaoc+BhvHqttbA/FO4Ty+y/S5hW6Ci/CTScG7vam4RYJWZxdTElc3MEfHRVH6cgQ==",
"version": "3.0.14",
"resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.14.tgz",
"integrity": "sha512-7ZaWZJOjUxa5hgmuMspyt8v/zVsh0GXYuF7OvCmdcbVa/xbnKQoYC+uYKunAqRGTkxjOyuOCw9rmFUFOqqC0eQ==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/node-config-provider": "^3.1.4",
"@smithy/protocol-http": "^4.1.0",
"@smithy/service-error-classification": "^3.0.3",
"@smithy/smithy-client": "^3.2.0",
"@smithy/smithy-client": "^3.1.12",
"@smithy/types": "^3.3.0",
"@smithy/util-middleware": "^3.0.3",
"@smithy/util-retry": "^3.0.3",
@@ -2078,9 +2075,9 @@
}
},
"node_modules/@smithy/smithy-client": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz",
"integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==",
"version": "3.1.12",
"resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.1.12.tgz",
"integrity": "sha512-wtm8JtsycthkHy1YA4zjIh2thJgIQ9vGkoR639DBx5lLlLNU0v4GARpQZkr2WjXue74nZ7MiTSWfVrLkyD8RkA==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/middleware-endpoint": "^3.1.0",
@@ -2171,13 +2168,13 @@
}
},
"node_modules/@smithy/util-defaults-mode-browser": {
"version": "3.0.15",
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.15.tgz",
"integrity": "sha512-FZ4Psa3vjp8kOXcd3HJOiDPBCWtiilLl57r0cnNtq/Ga9RSDrM5ERL6xt+tO43+2af6Pn5Yp92x2n5vPuduNfg==",
"version": "3.0.14",
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.14.tgz",
"integrity": "sha512-0iwTgKKmAIf+vFLV8fji21Jb2px11ktKVxbX6LIDPAUJyWQqGqBVfwba7xwa1f2FZUoolYQgLvxQEpJycXuQ5w==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/property-provider": "^3.1.3",
"@smithy/smithy-client": "^3.2.0",
"@smithy/smithy-client": "^3.1.12",
"@smithy/types": "^3.3.0",
"bowser": "^2.11.0",
"tslib": "^2.6.2"
@@ -2187,16 +2184,16 @@
}
},
"node_modules/@smithy/util-defaults-mode-node": {
"version": "3.0.15",
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.15.tgz",
"integrity": "sha512-KSyAAx2q6d0t6f/S4XB2+3+6aQacm3aLMhs9aLMqn18uYGUepbdssfogW5JQZpc6lXNBnp0tEnR5e9CEKmEd7A==",
"version": "3.0.14",
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.14.tgz",
"integrity": "sha512-e9uQarJKfXApkTMMruIdxHprhcXivH1flYCe8JRDTzkkLx8dA3V5J8GZlST9yfDiRWkJpZJlUXGN9Rc9Ade3OQ==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/config-resolver": "^3.0.5",
"@smithy/credential-provider-imds": "^3.2.0",
"@smithy/node-config-provider": "^3.1.4",
"@smithy/property-provider": "^3.1.3",
"@smithy/smithy-client": "^3.2.0",
"@smithy/smithy-client": "^3.1.12",
"@smithy/types": "^3.3.0",
"tslib": "^2.6.2"
},
@@ -2692,15 +2689,15 @@
}
},
"node_modules/aws4": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz",
"integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==",
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.1.tgz",
"integrity": "sha512-u5w79Rd7SU4JaIlA/zFqG+gOiuq25q5VLyZ8E+ijJeILuTxVzZgp2CaGw/UTw6pXYN9XMO9yiqj/nEHmhTG5CA==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.7.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
"integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
"integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
@@ -2965,10 +2962,9 @@
}
},
"node_modules/chart.js": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.4.tgz",
"integrity": "sha512-emICKGBABnxhMjUjlYRR12PmOXhJ2eJjEHL2/dZlWjxRAZT1D8xplLFq5M0tMQK8ja+wBS/tuVEJB5C6r7VxJA==",
"license": "MIT",
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.3.tgz",
"integrity": "sha512-qK1gkGSRYcJzqrrzdR6a+I0vQ4/R+SoODXyAjscQ/4mzuNzySaMCd+hyVxitSY1+L2fjPD1Gbn+ibNqRmwQeLw==",
"dependencies": {
"@kurkle/color": "^0.3.0"
},
@@ -3460,10 +3456,9 @@
"integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
},
"node_modules/debug": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
"license": "MIT",
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
"dependencies": {
"ms": "2.1.2"
},
@@ -4112,9 +4107,9 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/firebase-admin": {
"version": "12.4.0",
"resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-12.4.0.tgz",
"integrity": "sha512-3HOHqJxNmFv0JgK3voyMQgmcibhJN4LQfZfhnZGb6pcONnZxejki4nQ1twsoJlGaIvgQWBtO7rc5mh/cqlOJNA==",
"version": "12.3.1",
"resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-12.3.1.tgz",
"integrity": "sha512-vEr3s3esl8nPIA9r/feDT4nzIXCfov1CyyCSpMQWp6x63Q104qke0MEGZlrHUZVROtl8FLus6niP/M9I1s4VBA==",
"license": "Apache-2.0",
"dependencies": {
"@fastify/busboy": "^3.0.0",
@@ -5505,12 +5500,11 @@
}
},
"node_modules/node-mailjet": {
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/node-mailjet/-/node-mailjet-6.0.6.tgz",
"integrity": "sha512-cr8ciqtHuxyFd3+3bpDy+oKuNzctZfRQZtwRjurVAzE+DZLTfyxjgD+GTqQ1kr0ClAjDjSh3ERlZvd5MV0fKHg==",
"license": "MIT",
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/node-mailjet/-/node-mailjet-6.0.5.tgz",
"integrity": "sha512-upufsTkMyrDF7Z6OiJ4M4Yw4L6MkB0vOQB27W1V9q0CxxSA6e2xOJif3koPwwwgDELpbJNG7asZjKFdghtxUCw==",
"dependencies": {
"axios": "1.7.4",
"axios": "1.6.2",
"json-bigint": "^1.0.0",
"url-join": "^4.0.0"
},
@@ -5520,12 +5514,11 @@
}
},
"node_modules/node-mailjet/node_modules/axios": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
"integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
"license": "MIT",
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
"dependencies": {
"follow-redirects": "^1.15.6",
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
@@ -5540,10 +5533,9 @@
}
},
"node_modules/nodemailer": {
"version": "6.9.15",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.15.tgz",
"integrity": "sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ==",
"license": "MIT-0",
"version": "6.9.14",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.14.tgz",
"integrity": "sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==",
"engines": {
"node": ">=6.0.0"
}
@@ -5805,10 +5797,9 @@
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
},
"node_modules/phone": {
"version": "3.1.50",
"resolved": "https://registry.npmjs.org/phone/-/phone-3.1.50.tgz",
"integrity": "sha512-TRmb2bX3sX+rrOrc8FRd8hmy4exoH2Lu3vjBP/dLgwwci1lv7DbjJ2iHMe7X4Hm8Pa0rJcfqTbq/O1vjU4NgxQ==",
"license": "MIT",
"version": "3.1.49",
"resolved": "https://registry.npmjs.org/phone/-/phone-3.1.49.tgz",
"integrity": "sha512-S+rHWXSQrllK5eQwz0sDbwfxQ2PzennWPgsP/jdpEPH3k7P5IBJZYjvYfU8e/RF5AwKCgOtzbTGTGJcBSLJVVw==",
"engines": {
"node": ">=12"
}
@@ -6380,14 +6371,14 @@
}
},
"node_modules/soap": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/soap/-/soap-1.1.3.tgz",
"integrity": "sha512-yaVQ/a3uAJZesW4CFuU7reF3ip3xQDaGMm2VnMfZoVPOEws4jbg/CQJ3+kZ7WOICMH6vt4Mu+Ra04dZjJEXxww==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/soap/-/soap-1.1.1.tgz",
"integrity": "sha512-Mxj/nQ9oO+zYiVZqk9AiXgeHkX/xj8EAnbri9BkxGoMrWw3fKtQulaquSbIO+kgoZfm7g08xddtmpOG2H+Z0zQ==",
"license": "MIT",
"dependencies": {
"axios": "^1.7.7",
"axios": "^1.7.2",
"axios-ntlm": "^1.4.2",
"debug": "^4.3.6",
"debug": "^4.3.5",
"formidable": "^3.5.1",
"get-stream": "^6.0.1",
"lodash": "^4.17.21",

View File

@@ -19,17 +19,17 @@
"makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\""
},
"dependencies": {
"@aws-sdk/client-secrets-manager": "^3.637.0",
"@aws-sdk/client-ses": "^3.637.0",
"@aws-sdk/credential-provider-node": "^3.637.0",
"@opensearch-project/opensearch": "^2.12.0",
"aws4": "^1.13.2",
"axios": "^1.7.7",
"@aws-sdk/client-secrets-manager": "^3.629.0",
"@aws-sdk/client-ses": "^3.629.0",
"@aws-sdk/credential-provider-node": "^3.629.0",
"@opensearch-project/opensearch": "^2.11.0",
"aws4": "^1.13.1",
"axios": "^1.7.4",
"better-queue": "^3.8.12",
"bluebird": "^3.7.2",
"body-parser": "^1.20.2",
"canvas": "^2.11.2",
"chart.js": "^4.4.4",
"chart.js": "^4.4.3",
"cloudinary": "^2.4.0",
"compression": "^1.7.4",
"cookie-parser": "^1.4.6",
@@ -38,7 +38,7 @@
"dinero.js": "^1.9.1",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"firebase-admin": "^12.4.0",
"firebase-admin": "^12.3.1",
"graphql": "^16.9.0",
"graphql-request": "^6.1.0",
"graylog2": "^0.2.1",
@@ -49,13 +49,13 @@
"moment": "^2.30.1",
"moment-timezone": "^0.5.45",
"multer": "^1.4.5-lts.1",
"node-mailjet": "^6.0.6",
"node-mailjet": "^6.0.5",
"node-persist": "^4.0.3",
"nodemailer": "^6.9.15",
"phone": "^3.1.50",
"nodemailer": "^6.9.14",
"phone": "^3.1.49",
"recursive-diff": "^1.0.9",
"rimraf": "^6.0.1",
"soap": "^1.1.3",
"soap": "^1.1.1",
"socket.io": "^4.7.5",
"ssh2-sftp-client": "^10.0.3",
"twilio": "^4.23.0",

View File

@@ -94,7 +94,10 @@ exports.default = async (req, res) => {
ret.push({
billid: bill.id,
success: false,
errorMessage: (error && error.authResponse && error.authResponse.body) || (error && error.message)
errorMessage:
(error && error.authResponse && error.authResponse.body) ||
error.response?.data?.Fault?.Error.map((e) => e.Detail).join(", ") ||
(error && error.message)
});
//Add the export log error.
@@ -209,14 +212,14 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop)
AccountBasedExpenseLineDetail: {
...(bill.job.class ? { ClassRef: { value: classes[bill.job.class] } } : {}),
AccountRef: {
value: accounts[bodyshop.md_responsibility_centers.taxes.federal.accountdesc]
value: accounts[bodyshop.md_responsibility_centers.taxes.federal_itc.accountdesc]
}
},
Amount: Dinero({
amount: Math.round(
bill.billlines.reduce((acc, val) => {
return acc + val.actual_cost * val.quantity;
return acc + val.applicable_taxes?.federal ? (val.actual_cost * val.quantity ?? 0) : 0;
}, 0) * 100
)
})
@@ -274,6 +277,8 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop)
} catch (error) {
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
error: error, //(error && error.authResponse && error.authResponse.body) || (error && error.message),
validationError: JSON.stringify(error?.response?.data),
accountmeta: JSON.stringify({ accounts, taxCodes, classes }),
method: "InsertBill"
});
throw error;

View File

@@ -179,7 +179,11 @@ exports.default = async (req, res) => {
ret.push({
jobid: job.id,
success: false,
errorMessage: (error && error.authResponse && error.authResponse.body) || (error && error.message)
errorMessage:
error?.authResponse?.body ||
error?.response?.data?.Fault?.Error.map((e) => e.Detail).join(", ") ||
error?.response?.data ||
error?.message
});
console.log(error);
logger.log("qbo-receivable-create-error", "ERROR", req.user.email, {
@@ -254,7 +258,6 @@ async function InsertInsuranceCo(oauthClient, qbo_realmId, req, job, bodyshop) {
throw new Error(
`Insurance Company '${job.ins_co_nm}' not found in shop configuration. Please make sure it exists or change the insurance company name on the job to one that exists.`
);
return;
}
const Customer = {
DisplayName: job.ins_co_nm.trim(),
@@ -575,7 +578,9 @@ async function InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, paren
} catch (error) {
logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
error,
method: "InsertOwner"
method: "InsertInvoice",
validationError: JSON.stringify(error?.response?.data),
accountmeta: JSON.stringify({ items, taxCodes, classes })
});
throw error;
}

View File

@@ -96,7 +96,21 @@ const sendServerEmail = async ({ subject, text }) => {
}
};
const sendTaskEmail = async ({ to, subject, text, attachments }) => {
const sendProManagerWelcomeEmail = async ({to, subject, html}) => {
try {
await transporter.sendMail({
from: `ProManager <noreply@promanager.web-est.com>`,
to,
subject,
html
});
} catch (error) {
console.log(error);
logger.log("server-email-failure", "error", null, null, error);
}
};
const sendTaskEmail = async ({ to, subject, type = "text", html, text, attachments }) => {
try {
transporter.sendMail(
{
@@ -107,7 +121,7 @@ const sendTaskEmail = async ({ to, subject, text, attachments }) => {
}),
to: to,
subject: subject,
text: text,
...(type === "text" ? { text } : { html }),
attachments: attachments || null
},
(err, info) => {
@@ -309,5 +323,6 @@ module.exports = {
sendEmail,
sendServerEmail,
sendTaskEmail,
sendProManagerWelcomeEmail,
emailBounce
};

View File

@@ -94,8 +94,9 @@ const formatPriority = (priority) => {
* @param taskId
* @returns {{header, body: string, subHeader: string}}
*/
const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, job, taskId) => {
const endPoints = InstanceManager({
const getEndpoints = () =>
InstanceManager({
imex: process.env?.NODE_ENV === "test" ? "https://test.imex.online" : "https://imex.online",
rome:
bodyshop.convenient_company === "promanager"
@@ -106,6 +107,9 @@ const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, j
? "https//test.romeonline.io"
: "https://romeonline.io"
});
const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, job, taskId) => {
const endPoints = getEndpoints();
return {
header: title,
subHeader: `Body Shop: ${bodyshop.shopname} | Priority: ${formatPriority(priority)} ${formatDate(dueDate)}`,
@@ -333,5 +337,6 @@ const tasksRemindEmail = async (req, res) => {
module.exports = {
taskAssignedEmail,
tasksRemindEmail
tasksRemindEmail,
getEndpoints
};

View File

@@ -1,30 +1,28 @@
const admin = require("firebase-admin");
const logger = require("../utils/logger");
const path = require("path");
const { auth } = require("firebase-admin");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const client = require("../graphql-client/graphql-client").client;
const admin = require("firebase-admin");
const logger = require("../utils/logger");
const { sendProManagerWelcomeEmail } = require("../email/sendemail");
const client = require("../graphql-client/graphql-client").client;
const serviceAccount = require(process.env.FIREBASE_ADMINSDK_JSON);
const adminEmail = require("../utils/adminEmail");
const generateEmailTemplate = require("../email/generateTemplate");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: process.env.FIREBASE_DATABASE_URL
});
exports.admin = admin;
exports.createUser = async (req, res) => {
const createUser = async (req, res) => {
logger.log("admin-create-user", "ADMIN", req.user.email, null, {
request: req.body,
ioadmin: true
});
const { email, displayName, password, shopid, authlevel } = req.body;
const { email, displayName, password, shopid, authlevel, validemail } = req.body;
try {
const userRecord = await admin.auth().createUser({ email, displayName, password });
@@ -42,6 +40,7 @@ exports.createUser = async (req, res) => {
user: {
email: email.toLowerCase(),
authid: userRecord.uid,
validemail,
associations: {
data: [{ shopid, authlevel, active: true }]
}
@@ -58,21 +57,115 @@ exports.createUser = async (req, res) => {
}
};
exports.updateUser = (req, res) => {
const sendPromanagerWelcomeEmail = (req, res) => {
const { authid, email } = req.body;
// Fetch user from Firebase
admin
.auth()
.getUser(authid)
.then((userRecord) => {
if (!userRecord) {
return Promise.reject({ status: 404, message: "User not found in Firebase." });
}
// Fetch user data from the database using GraphQL
return client.request(
`
query GET_USER_BY_EMAIL($email: String!) {
users(where: { email: { _eq: $email } }) {
email
validemail
associations {
id
shopid
bodyshop {
id
convenient_company
}
}
}
}`,
{ email: email.toLowerCase() }
);
})
.then((dbUserResult) => {
const dbUser = dbUserResult?.users?.[0];
if (!dbUser) {
return Promise.reject({ status: 404, message: "User not found in database." });
}
// Validate email before proceeding
if (!dbUser.validemail) {
logger.log("admin-send-welcome-email-skip", "ADMIN", req.user.email, null, {
message: "User email is not valid, skipping email.",
email
});
return res.status(200).json({ message: "User email is not valid, email not sent." });
}
// Check if the user's company is ProManager
const convenientCompany = dbUser.associations?.[0]?.bodyshop?.convenient_company;
if (convenientCompany !== "promanager") {
logger.log("admin-send-welcome-email-skip", "ADMIN", req.user.email, null, {
message: 'convenient_company is not "promanager", skipping email.',
convenientCompany
});
return res.status(200).json({ message: `convenient_company is not "promanager", email not sent.` });
}
// Generate password reset link
return admin
.auth()
.generatePasswordResetLink(dbUser.email)
.then((resetLink) => ({ dbUser, resetLink }));
})
.then(({ dbUser, resetLink }) => {
// Send welcome email (replace with your actual email-sending service)
return sendProManagerWelcomeEmail({
to: dbUser.email,
subject: "Welcome to the ProManager platform.",
html: generateEmailTemplate({
header: "",
subHeader: "",
body: `
<p>Welcome to the ProManager platform. Please click the link below to reset your password:</p>
<p><a href="${resetLink}">Reset your password</a></p>
<p>User Details:</p>
<ul>
<li>Email: ${dbUser.email}</li>
</ul>
`
})
});
})
.then(() => {
// Log success and return response
logger.log("admin-send-welcome-email", "ADMIN", req.user.email, null, {
request: req.body,
ioadmin: true,
emailSentTo: email
});
res.status(200).json({ message: "Welcome email sent successfully." });
})
.catch((error) => {
logger.log("admin-send-welcome-email-error", "ERROR", req.user.email, null, { error });
if (!res.headersSent) {
res.status(error.status || 500).json({
message: error.message || "Error sending welcome email.",
error
});
}
});
};
const updateUser = (req, res) => {
logger.log("admin-update-user", "ADMIN", req.user.email, null, {
request: req.body,
ioadmin: true
});
if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) {
logger.log("admin-update-user-unauthorized", "ERROR", req.user.email, null, {
request: req.body,
user: req.user
});
res.sendStatus(404);
return;
}
admin
.auth()
.updateUser(
@@ -105,26 +198,45 @@ exports.updateUser = (req, res) => {
});
};
exports.getUser = (req, res) => {
const getUser = (req, res) => {
logger.log("admin-get-user", "ADMIN", req.user.email, null, {
request: req.body,
ioadmin: true
});
if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) {
logger.log("admin-update-user-unauthorized", "ERROR", req.user.email, null, {
request: req.body,
user: req.user
});
res.sendStatus(404);
return;
}
admin
.auth()
.getUser(req.body.uid)
.then((userRecord) => {
res.json(userRecord);
return client
.request(
`
query GET_USER_BY_AUTHID($authid: String!) {
users(where: { authid: { _eq: $authid } }) {
email
validemail
associations {
id
shopid
bodyshop {
id
convenient_company
}
}
}
}
`,
{ authid: req.body.uid }
)
.then((dbUserResult) => {
res.json({
...userRecord,
db: {
validemail: dbUserResult?.users?.[0]?.validemail,
company: dbUserResult?.users?.[0]?.associations?.[0]?.bodyshop?.convenient_company
}
});
});
})
.catch((error) => {
logger.log("admin-get-user-error", "ERROR", req.user.email, null, {
@@ -134,7 +246,7 @@ exports.getUser = (req, res) => {
});
};
exports.sendNotification = async (req, res) => {
const sendNotification = async (req, res) => {
setTimeout(() => {
// Send a message to the device corresponding to the provided
// registration token.
@@ -167,7 +279,7 @@ exports.sendNotification = async (req, res) => {
}, 500);
};
exports.subscribe = async (req, res) => {
const subscribe = async (req, res) => {
const result = await admin
.messaging()
.subscribeToTopic(req.body.fcm_tokens, `${req.body.imexshopid}-${req.body.type}`);
@@ -175,7 +287,7 @@ exports.subscribe = async (req, res) => {
res.json(result);
};
exports.unsubscribe = async (req, res) => {
const unsubscribe = async (req, res) => {
try {
const result = await admin
.messaging()
@@ -187,6 +299,17 @@ exports.unsubscribe = async (req, res) => {
}
};
module.exports = {
admin,
createUser,
updateUser,
getUser,
sendPromanagerWelcomeEmail,
sendNotification,
subscribe,
unsubscribe
};
//Admin claims code.
// const uid = "JEqqYlsadwPEXIiyRBR55fflfko1";

View File

@@ -2502,6 +2502,13 @@ exports.GET_JOBS_BY_PKS = `query GET_JOBS_BY_PKS($ids: [uuid!]!) {
jobs(where: {id: {_in: $ids}}) {
id
shopid
ro_number
ownr_co_nm
ownr_fn
ownr_ln
v_make_desc
v_model_yr
v_model_desc
}
}
`;

View File

@@ -7,7 +7,9 @@ const axios = require("axios");
const moment = require("moment");
const logger = require("../utils/logger");
const InstanceManager = require("../utils/instanceMgr").default;
const { sendTaskEmail } = require("../email/sendemail");
const generateEmailTemplate = require("../email/generateTemplate");
const { getEndpoints } = require("../email/tasksEmails");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
@@ -129,6 +131,7 @@ exports.generate_payment_url = async (req, res) => {
//...req.body,
amount: Dinero({ amount: Math.round(req.body.amount * 100) }).toFormat("0.00"),
account: req.body.account,
comment: req.body.comment,
invoice: req.body.invoice,
createshorturl: true
//The postback URL is set at the CP teller global terminal settings page.
@@ -162,7 +165,67 @@ exports.postback = async (req, res) => {
return;
}
if (values.invoice) {
if (comment) {
//Shifted the order to have this first to retain backwards compatibility for the old style of short link.
//This has been triggered by IO and may have multiple jobs.
const parsedComment = JSON.parse(comment);
//Adding in the user email to the short pay email.
//Need to check this to ensure backwards compatibility for clients that don't update.
const partialPayments = Array.isArray(parsedComment) ? parsedComment : parsedComment.payments;
const jobs = await gqlClient.request(queries.GET_JOBS_BY_PKS, {
ids: partialPayments.map((p) => p.jobid)
});
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
paymentInput: partialPayments.map((p) => ({
amount: p.amount,
transactionid: values.authcode,
payer: "Customer",
type: values.cardtype,
jobid: p.jobid,
date: moment(Date.now()),
payment_responses: {
data: {
amount: values.total,
bodyshopid: jobs.jobs[0].shopid,
jobid: p.jobid,
declinereason: "Approved",
ext_paymentid: values.paymentid,
successful: true,
response: values
}
}
}))
});
logger.log("intellipay-postback-app-success", "DEBUG", req.user?.email, null, {
iprequest: values,
paymentResult
});
if (values.origin === "OneLink" && parsedComment.userEmail) {
//Send an email, it was a text to pay link.
const endPoints = getEndpoints();
sendTaskEmail({
to: parsedComment.userEmail,
subject: `New Payment(s) Received - RO ${jobs.jobs.map((j) => j.ro_number).join(", ")}`,
type: "html",
html: generateEmailTemplate({
header: "New Payment(s) Received",
subHeader: "",
body: jobs.jobs
.map(
(job) =>
`Reference: <a href="${endPoints}/manage/jobs/${job.id}">${job.ro_number || "N/A"}</a> | ${job.ownr_co_nm ? job.ownr_co_nm : `${job.ownr_fn || ""} ${job.ownr_ln || ""}`.trim()} | ${`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`.trim()} | $${partialPayments.find((p) => p.jobid === job.id).amount}`
)
.join("<br/>")
})
});
res.sendStatus(200);
}
} else if (values.invoice) {
//This is a link email that's been sent out.
const job = await gqlClient.request(queries.GET_JOB_BY_PK, {
id: values.invoice
@@ -198,39 +261,6 @@ exports.postback = async (req, res) => {
paymentResult
});
res.sendStatus(200);
} else if (comment) {
//This has been triggered by IO and may have multiple jobs.
const partialPayments = JSON.parse(comment);
const jobs = await gqlClient.request(queries.GET_JOBS_BY_PKS, {
ids: partialPayments.map((p) => p.jobid)
});
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
paymentInput: partialPayments.map((p) => ({
amount: p.amount,
transactionid: values.authcode,
payer: "Customer",
type: values.cardtype,
jobid: p.jobid,
date: moment(Date.now()),
payment_responses: {
data: {
amount: values.total,
bodyshopid: jobs.jobs[0].shopid,
jobid: p.jobid,
declinereason: "Approved",
ext_paymentid: values.paymentid,
successful: true,
response: values
}
}
}))
});
logger.log("intellipay-postback-app-success", "DEBUG", req.user?.email, null, {
iprequest: values,
paymentResult
});
res.sendStatus(200);
}
} catch (error) {
logger.log("intellipay-postback-error", "ERROR", req.user?.email, null, {

View File

@@ -1,18 +1,20 @@
const express = require("express");
const router = express.Router();
const fb = require("../firebase/firebase-handler");
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
const { createAssociation, createShop, updateShop, updateCounter } = require("../admin/adminops");
const { updateUser, getUser, createUser, sendPromanagerWelcomeEmail } = require("../firebase/firebase-handler");
const validateAdminMiddleware = require("../middleware/validateAdminMiddleware");
router.use(validateFirebaseIdTokenMiddleware);
router.use(validateAdminMiddleware);
router.post("/createassociation", validateAdminMiddleware, createAssociation);
router.post("/createshop", validateAdminMiddleware, createShop);
router.post("/updateshop", validateAdminMiddleware, updateShop);
router.post("/updatecounter", validateAdminMiddleware, updateCounter);
router.post("/updateuser", fb.updateUser);
router.post("/getuser", fb.getUser);
router.post("/createuser", fb.createUser);
router.post("/createassociation", createAssociation);
router.post("/createshop", createShop);
router.post("/updateshop", updateShop);
router.post("/updatecounter", updateCounter);
router.post("/updateuser", updateUser);
router.post("/getuser", getUser);
router.post("/createuser", createUser);
router.post("/promanagerwelcome", sendPromanagerWelcomeEmail);
module.exports = router;