RO into IO merge as of 02/05/2024.

This commit is contained in:
Patrick Fic
2024-02-12 12:22:05 -08:00
211 changed files with 31134 additions and 25729 deletions

View File

@@ -6,7 +6,7 @@ orbs:
aws-cli: circleci/aws-cli@4.0
eb: circleci/aws-elastic-beanstalk@2.0.1
jobs:
api-deploy:
imex-api-deploy:
docker:
- image: cimg/node:18.18.2
steps:
@@ -19,9 +19,9 @@ jobs:
eb deploy
eb status
hasura-migrate:
imex-hasura-migrate:
docker:
- image: cimg/node:16.15.0
- image: cimg/node:18.18.2
parameters:
secret:
type: string
@@ -38,30 +38,10 @@ jobs:
hasura metadata apply --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
hasura metadata reload --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
app-build:
docker:
- image: cimg/node:16.15.0
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- run:
name: Install Dependencies
command: npm i
- run: npm run build
- aws-s3/sync:
from: build
to: "s3://imex-online-production/"
arguments: "--exclude '*.map'"
app-beta-build:
imex-app-build:
docker:
- image: cimg/node:18.18.2
resource_class: large
working_directory: ~/repo/client
steps:
@@ -71,7 +51,7 @@ jobs:
name: Install Dependencies
command: npm i
- run: npm run build
- run: npm run build:imex
- aws-cli/setup:
aws_access_key_id: AWS_ACCESS_KEY_ID
@@ -80,9 +60,106 @@ jobs:
- aws-s3/sync:
from: build
to: "s3://imex-online-beta/"
to: "s3://imex-online-production/"
arguments: "--exclude '*.map'"
rome-api-deploy:
docker:
- image: "cimg/base:stable"
steps:
- checkout
- eb/setup
- run:
command: |
eb init romeonline-productionapi -r us-east-2 -p "Node.js 18 running on 64bit Amazon Linux 2"
eb status --verbose
eb deploy
eb status
rome-hasura-migrate:
docker:
- image: cimg/node:18.18.2
parameters:
secret:
type: string
default: $HASURA_ROME_PROD_SECRET
working_directory: ~/repo/hasura
steps:
- checkout:
path: ~/repo
- run:
name: Execute migration
command: |
npm install hasura-cli -g
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 >>
rome-app-build:
docker:
- image: cimg/node:18.18.2
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- run:
name: Install Dependencies
command: npm i
- run: npm run build:rome
- aws-cli/setup:
aws_access_key_id: AWS_ACCESS_KEY_ID
aws_secret_access_key: AWS_SECRET_ACCESS_KEY
region: AWS_REGION
- aws-s3/sync:
from: build
to: "s3://rome-online-production/"
arguments: "--exclude '*.map'"
test-rome-hasura-migrate:
docker:
- image: cimg/node:16.15.0
parameters:
secret:
type: string
default: $HASURA_ROME_TEST_SECRET
working_directory: ~/repo/hasura
steps:
- checkout:
path: ~/repo
- run:
name: Execute migration
command: |
npm install hasura-cli -g
hasura migrate apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
hasura metadata apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
hasura metadata reload --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
test-rome-app-build:
docker:
- image: cimg/node:18.18.2
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- run:
name: Install Dependencies
command: npm i
- run: npm run build:test:rome
- aws-s3/sync:
from: build
to: "s3://rome-online-test/"
arguments: "--exclude '*.map'"
test-hasura-migrate:
docker:
- image: cimg/node:16.15.0
@@ -98,12 +175,11 @@ jobs:
name: Execute migration
command: |
npm install hasura-cli -g
echo ${HASURA_TEST_SECRET}
hasura migrate apply --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
hasura metadata apply --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
hasura metadata reload --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
test-app-build:
imex-test-app-build:
docker:
- image: cimg/node:16.15.0
@@ -116,40 +192,13 @@ jobs:
name: Install Dependencies
command: npm i
- run: npm run build:test
- run: npm run build:test:imex
- aws-s3/sync:
from: build
to: "s3://imex-online-test/"
arguments: "--exclude '*.map'"
test-app-beta-build:
docker:
- image: cimg/node:18.18.2
resource_class: large
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- run:
name: Install Dependencies
command: npm i
- run: npm run build:test
- run: npm run sentry:sourcemaps
- aws-cli/setup:
aws_access_key_id: AWS_ACCESS_KEY_ID
aws_secret_access_key: AWS_SECRET_ACCESS_KEY
region: AWS_REGION
- aws-s3/sync:
from: build
to: "s3://imex-online-test-beta/"
arguments: "--exclude '*.map'"
admin-app-build:
docker:
@@ -184,37 +233,52 @@ jobs:
workflows:
deploy_and_build:
jobs:
- api-deploy:
- imex-api-deploy:
filters:
branches:
only: master
- app-build:
- imex-app-build:
filters:
branches:
only: master
- app-beta-build:
filters:
branches:
only: master-beta
- hasura-migrate:
secret: ${HASURA_PROD_SECRET}
filters:
branches:
only: master
- test-app-build:
- rome-api-deploy:
filters:
branches:
only: rome/master
- rome-app-build:
filters:
branches:
only: rome/master
- rome-hasura-migrate:
secret: ${HASURA_PROD_SECRET}
filters:
branches:
only: rome/master
- imex-test-app-build:
filters:
branches:
only: test
- test-app-beta-build:
filters:
branches:
only: test-beta
- test-hasura-migrate:
secret: ${HASURA_TEST_SECRET}
filters:
branches:
only: test
- test-rome-app-build:
filters:
branches:
only: rome/test
- test-rome-hasura-migrate:
secret: ${HASURA_ROME_TEST_SECRET}
filters:
branches:
only: rome/test
#- admin-app-build:
#filters:
#branches:
#only: master
#only: master

File diff suppressed because it is too large Load Diff

View File

@@ -10,4 +10,5 @@ REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=http://localhost:4000
REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
INSTANCE=IMEX

View File

@@ -0,0 +1,14 @@
REACT_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
REACT_APP_GA_CODE=231099835
REACT_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"}
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
REACT_APP_CLOUDINARY_API_KEY=957865933348715
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=http://localhost:4000
REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
INSTANCE=PROMANAGER

View File

@@ -0,0 +1,15 @@
REACT_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
REACT_APP_GA_CODE=231099835
REACT_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"}
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
REACT_APP_CLOUDINARY_API_KEY=957865933348715
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=http://localhost:4000
REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
REACT_APP_COUNTRY=USA
INSTANCE=ROME

View File

@@ -11,4 +11,5 @@ REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BMgZT1NZztW2DsJl8Mg2L04hgY9FzAg6b8fbzgNAfww
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=https://api.imex.online/
REACT_APP_REPORTS_SERVER_URL=https://reports.imex.online
REACT_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
REACT_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
INSTANCE=IMEX

View File

@@ -0,0 +1,15 @@
GENERATE_SOURCEMAP=true
REACT_APP_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.imex.online/v1/graphql
REACT_APP_GA_CODE=231103507
REACT_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU","authDomain":"imex-prod.firebaseapp.com","databaseURL":"https://imex-prod.firebaseio.com","projectId":"imex-prod","storageBucket":"imex-prod.appspot.com","messagingSenderId":"253497221485","appId":"1:253497221485:web:3c81c483b94db84b227a64","measurementId":"G-NTWBKG2L0M"}
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
REACT_APP_CLOUDINARY_API_KEY=473322739956866
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BMgZT1NZztW2DsJl8Mg2L04hgY9FzAg6b8fbzgNAfww2VDzH3VE63Ot9EaP_U7KWS2JT-7HPHaw0T_Tw_5vkZc8'
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=https://api.imex.online/
REACT_APP_REPORTS_SERVER_URL=https://reports.imex.online
REACT_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
INSTANCE=PROMANAGER

View File

@@ -0,0 +1,15 @@
GENERATE_SOURCEMAP=true
REACT_APP_GRAPHQL_ENDPOINT=https://db.romeonline.io/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.romeonline.io/v1/graphql
REACT_APP_GA_CODE=231103507
REACT_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"}
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
REACT_APP_CLOUDINARY_API_KEY=473322739956866
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=https://api.romeonline.io/
REACT_APP_REPORTS_SERVER_URL=https://reports.romeonline.io
REACT_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
INSTANCE=ROME

View File

@@ -11,4 +11,5 @@ REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxw
REACT_APP_AXIOS_BASE_API_URL=https://api.test.imex.online/
REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
REACT_APP_IS_TEST=true
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
INSTANCE=IMEX

View File

@@ -0,0 +1,15 @@
REACT_APP_GRAPHQL_ENDPOINT=https://db.test.bodyshop.app/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.bodyshop.app/v1/graphql
REACT_APP_GA_CODE=231099835
REACT_APP_FIREBASE_CONFIG={ "apiKey":"AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c", "authDomain":"imex-test.firebaseapp.com", "projectId":"imex-test", "storageBucket":"imex-test.appspot.com", "messagingSenderId":"991923618608", "appId":"1:991923618608:web:633437569cdad78299bef5", "measurementId":"G-TW0XLZEH18"}
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
REACT_APP_CLOUDINARY_API_KEY=473322739956866
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BN2GcDPjipR5MTEosO5dT4CfQ3cmrdBIsI4juoOQrRijn_5aRiHlwj1mlq0W145mOusx6xynEKl_tvYJhpCc9lo'
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=https://api.test.imex.online/
REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
REACT_APP_IS_TEST=true
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
INSTANCE=PROMANAGER

15
client/.env.test.rome Normal file
View File

@@ -0,0 +1,15 @@
REACT_APP_GRAPHQL_ENDPOINT=https://db.test.romeonline.io/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.romeonline.io/v1/graphql
REACT_APP_GA_CODE=231103507
REACT_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"}
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
REACT_APP_CLOUDINARY_API_KEY=473322739956866
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=https://api.test.romeonline.io/
REACT_APP_REPORTS_SERVER_URL=https://reports.test.romeonline.io
REACT_APP_IS_TEST=true
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
INSTANCE=ROME

View File

@@ -13,21 +13,7 @@ const v4Token = convertLegacyToken(mapToken);
module.exports = {
plugins: [
// {
// plugin: SentryWebpackPlugin,
// options: {
// // sentry-cli configuration
// authToken:
// "6b45b028a02342db97a9a2f92c0959058665443d379d4a3a876430009e744260",
// org: "snapt-software",
// project: "imexonline",
// release: process.env.REACT_APP_GIT_SHA,
//
// // webpack-specific configuration
// include: ".",
// ignore: ["node_modules", "webpack.config.js"],
// },
// },
{
plugin: CracoLessPlugin,
options: {

1493
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -89,15 +89,21 @@
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'",
"start": "craco start",
"start:imex": "dotenvx run --env-file=.env.development.imex -- craco start",
"start:rome": "dotenvx run --env-file=.env.development.rome -- craco start",
"start:promanager": "dotenvx run --env-file=.env.development.promanager -- craco start",
"build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
"build:test": "env-cmd -f .env.test npm run build",
"build-deploy:test": "npm run build:test && s3cmd sync build/* s3://imex-online-test && echo '🚀 TESTING Deployed!'",
"buildcra": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
"build:test:imex": "env-cmd -f .env.test.imex npm run build",
"build:test:rome": "env-cmd -f .env.test.rome npm run build",
"build:test:promanager": "env-cmd -f .env.test.promanager npm run build",
"build:production:imex": "env-cmd -f .env.production.imex npm run build",
"build:production:rome": "env-cmd -f .env.production.rome npm run build",
"build:production:promanager": "env-cmd -f .env.production.promanager npm run build",
"test": "cypress open",
"eject": "react-scripts eject",
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular .",
"eulaize": "node src/utils/eulaize.js",
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org imex --project imexonline ./build && sentry-cli sourcemaps upload --org imex --project imexonline ./build"
"sentry:sourcemaps:imex": "sentry-cli sourcemaps inject --org imex --project imexonline ./build && sentry-cli sourcemaps upload --org imex --project imexonline ./build"
},
"eslintConfig": {
"extends": [
@@ -123,6 +129,7 @@
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@dotenvx/dotenvx": "^0.15.0",
"@sentry/webpack-plugin": "^2.14.0",
"@testing-library/cypress": "^10.0.1",
"cypress": "^13.6.4",

File diff suppressed because one or more lines are too long

View File

@@ -28,6 +28,18 @@ switch (this.location.hostname) {
// measurementId: "${config.measurementId}",
};
break;
//TODO:AIO Update this for pro manager and include all .env files for server and client.
case "romeonline.io":
firebaseConfig = {
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",
};
break;
case "imex.online":
default:
firebaseConfig = {

View File

@@ -2,13 +2,88 @@
<html lang="en">
<head>
<meta charset="utf-8"/>
<link href="%PUBLIC_URL%/favicon.png" rel="icon"/>
<!-- TODO:AIO Change the favicon based on instance type. -->
<link href="%PUBLIC_URL%/ro-favicon.png" rel="icon"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<meta content="#002366" name="theme-color"/>
<meta content="ImEX Online" name="description"/>
<!-- TODO:AIO Change the title name based on instance. -->
<meta content="Rome Online" name="description"/>
<!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
<!-- TODO:AIO Need to verify that this will build based on the instance type. -->
<!-- TODO:AIO Show the code below if the instance is ROME. -->
<!--Use the below code snippet to provide real time updates to the live chat plugin without the need of copying and paste each time to your website when changes are made via PBX-->
<call-us-selector party="LiveChat528346" phonesystem-url=https://rometech.east.3cx.us:5001></call-us-selector>
<!--Incase you don't want real time updates to the live chat plugin when options are changed, use the below code snippet. Please note that each time you change the settings you will need to copy and paste the snippet code to your website-->
<!--<call-us
phonesystem-url=https://rometech.east.3cx.us:5001
style="position:fixed;font-size:16px;line-height:17px;z-index: 99999;right: 20px; bottom: 20px;"
id="wp-live-chat-by-3CX"
minimized="true"
animation-style="noanimation"
party="LiveChat528346"
minimized-style="bubbleright"
allow-call="true"
allow-video="false"
allow-soundnotifications="true"
enable-mute="true"
enable-onmobile="true"
offline-enabled="true"
enable="true"
ignore-queueownership="false"
authentication="both"
show-operator-actual-name="true"
aknowledge-received="true"
gdpr-enabled="false"
message-userinfo-format="name"
message-dateformat="both"
lang="browser"
button-icon-type="default"
greeting-visibility="none"
greeting-offline-visibility="none"
chat-delay="2000"
enable-direct-call="true"
enable-ga="false"
></call-us>-->
<script charset="utf-8" defer id="tcx-callus-js"
src=https://downloads-global.3cx.com/downloads/livechatandtalk/v1/callus.js></script>
<link href="logo192.png" rel="apple-touch-icon"/>
<script type="text/javascript">
<!-- TODO:AIO Show this script if instance type is IMEX -->
<script type="text/javascript">
window.$crisp = [];
window.CRISP_WEBSITE_ID = "36724f62-2eb0-4b29-9cdd-9905fb99913e";
(function () {
@@ -19,7 +94,7 @@
d.getElementsByTagName("head")[0].appendChild(s);
})();
</script>
<script>
<script>
!(function () {
"use strict";
var e = [
@@ -69,6 +144,7 @@
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<!-- TODO:AIO Update the manifest as a post-build step? -->
<link href="%PUBLIC_URL%/manifest.json" rel="manifest"/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
@@ -79,7 +155,8 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>ImEX Online</title>
<!-- TODO:AIO Update based on instance type. -->
<title>Rome Online</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

View File

@@ -1,6 +1,6 @@
{
"short_name": "ImEX Online",
"name": "ImEX Online",
"short_name": "Rome Online",
"name": "Rome Online",
"description": "The ultimate bodyshop management system.",
"icons": [
{

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

View File

@@ -1,11 +1,11 @@
import {useSplitClient} from "@splitsoftware/splitio-react";
import {Button, Result} from "antd";
import { useSplitClient } from "@splitsoftware/splitio-react";
import { Button, Result } from "antd";
import LogRocket from "logrocket";
import React, {lazy, Suspense, useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {Route, Routes} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import React, { lazy, Suspense, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Route, Routes } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import DocumentEditorContainer from "../components/document-editor/document-editor.container";
import ErrorBoundary from "../components/error-boundary/error-boundary.component";
@@ -14,15 +14,19 @@ import LoadingSpinner from "../components/loading-spinner/loading-spinner.compon
import DisclaimerPage from "../pages/disclaimer/disclaimer.page";
import LandingPage from "../pages/landing/landing.page";
import TechPageContainer from "../pages/tech/tech.page.container";
import {setOnline} from "../redux/application/application.actions";
import {selectOnline} from "../redux/application/application.selectors";
import {checkUserSession} from "../redux/user/user.actions";
import {selectBodyshop, selectCurrentEula, selectCurrentUser,} from "../redux/user/user.selectors";
import { setOnline } from "../redux/application/application.actions";
import { selectOnline } from "../redux/application/application.selectors";
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/betaHandler";
import Eula from "../components/eula/eula.component";
import InstanceRenderMgr from "../utils/instanceRenderMgr";
const ResetPassword = lazy(() =>
import("../pages/reset-password/reset-password.component")
);
@@ -37,18 +41,24 @@ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
online: selectOnline,
bodyshop: selectBodyshop,
currentEula: selectCurrentEula
currentEula: selectCurrentEula,
});
const mapDispatchToProps = (dispatch) => ({
checkUserSession: () => dispatch(checkUserSession()),
setOnline: (isOnline) => dispatch(setOnline(isOnline)),
});
export function App({bodyshop, checkUserSession, currentUser, online, setOnline, currentEula}) {
export function App({
bodyshop,
checkUserSession,
currentUser,
online,
setOnline,
currentEula,
}) {
const client = useSplitClient().client;
const [listenersAdded, setListenersAdded] = useState(false)
const {t} = useTranslation();
const [listenersAdded, setListenersAdded] = useState(false);
const { t } = useTranslation();
useEffect(() => {
if (!navigator.onLine) {
@@ -65,14 +75,14 @@ export function App({bodyshop, checkUserSession, currentUser, online, setOnline,
useEffect(() => {
const offlineListener = (e) => {
setOnline(false);
}
};
const onlineListener = (e) => {
setOnline(true);
}
};
if (!listenersAdded) {
console.log('Added events for offline and online');
console.log("Added events for offline and online");
window.addEventListener("offline", offlineListener);
window.addEventListener("online", onlineListener);
setListenersAdded(true);
@@ -81,7 +91,7 @@ export function App({bodyshop, checkUserSession, currentUser, online, setOnline,
return () => {
window.removeEventListener("offline", offlineListener);
window.removeEventListener("online", onlineListener);
}
};
}, [setOnline, listenersAdded]);
useEffect(() => {
@@ -90,16 +100,26 @@ export function App({bodyshop, checkUserSession, currentUser, online, setOnline,
if (
client.getTreatment("LogRocket_Tracking") === "on" ||
window.location.hostname === 'beta.imex.online'
window.location.hostname ===
InstanceRenderMgr({
imex: "beta.imex.online",
rome: "beta.romeonline.io",
})
) {
console.log("LR Start");
LogRocket.init("gvfvfw/bodyshopapp");
LogRocket.init(
InstanceRenderMgr({
imex: "gvfvfw/bodyshopapp",
rome: "rome-online/rome-online",
promanager: "", //TODO:AIO Add in log rocket for promanager instances.
})
);
}
}
}, [bodyshop, client, currentUser.authorized]);
if (currentUser.authorized === null) {
return <LoadingSpinner message={t("general.labels.loggingin")}/>;
return <LoadingSpinner message={t("general.labels.loggingin")} />;
}
handleBeta();
@@ -124,29 +144,102 @@ export function App({bodyshop, checkUserSession, currentUser, online, setOnline,
);
if (currentEula && !currentUser.eulaIsAccepted) {
return <Eula/>
return <Eula />;
}
// Any route that is not assigned and matched will default to the Landing Page component
return (
<Suspense fallback={<LoadingSpinner message="ImEX Online"/>}>
<Suspense
fallback={
<LoadingSpinner
message={InstanceRenderMgr({
imex: "ImEX Online",
rome: "Rome Online",
promanager: "ProManager",
})}
/>
}
>
<Routes>
<Route path="*" element={<ErrorBoundary><LandingPage/></ErrorBoundary>}/>
<Route path="/signin" element={<ErrorBoundary><SignInPage/></ErrorBoundary>}/>
<Route path="/resetpassword" element={<ErrorBoundary><ResetPassword/></ErrorBoundary>}/>
<Route path="/csi/:surveyId" element={<ErrorBoundary><CsiPage/></ErrorBoundary>}/>
<Route path="/disclaimer" element={<ErrorBoundary><DisclaimerPage/></ErrorBoundary>}/>
<Route path="/mp/:paymentIs" element={<ErrorBoundary><MobilePaymentContainer/></ErrorBoundary>}/>
<Route path="/manage/*"
element={<ErrorBoundary><PrivateRoute isAuthorized={currentUser.authorized}/></ErrorBoundary>}>
<Route path="*" element={<ManagePage/>}/>
<Route
path="*"
element={
<ErrorBoundary>
<LandingPage />
</ErrorBoundary>
}
/>
<Route
path="/signin"
element={
<ErrorBoundary>
<SignInPage />
</ErrorBoundary>
}
/>
<Route
path="/resetpassword"
element={
<ErrorBoundary>
<ResetPassword />
</ErrorBoundary>
}
/>
<Route
path="/csi/:surveyId"
element={
<ErrorBoundary>
<CsiPage />
</ErrorBoundary>
}
/>
<Route
path="/disclaimer"
element={
<ErrorBoundary>
<DisclaimerPage />
</ErrorBoundary>
}
/>
<Route
path="/mp/:paymentIs"
element={
<ErrorBoundary>
<MobilePaymentContainer />
</ErrorBoundary>
}
/>
<Route
path="/manage/*"
element={
<ErrorBoundary>
<PrivateRoute
isAuthorized={currentUser.authorized}
/>
</ErrorBoundary>
}
>
<Route path="*" element={<ManagePage />} />
</Route>
<Route path="/tech/*"
element={<ErrorBoundary><PrivateRoute isAuthorized={currentUser.authorized}/></ErrorBoundary>}>
<Route path="*" element={<TechPageContainer/>}/>
<Route
path="/tech/*"
element={
<ErrorBoundary>
<PrivateRoute
isAuthorized={currentUser.authorized}
/>
</ErrorBoundary>
}
>
<Route path="*" element={<TechPageContainer />} />
</Route>
<Route path="/edit/*" element={<PrivateRoute isAuthorized={currentUser.authorized}/>}>
<Route path="*" element={<DocumentEditorContainer/>}/>
<Route
path="/edit/*"
element={
<PrivateRoute isAuthorized={currentUser.authorized} />
}
>
<Route path="*" element={<DocumentEditorContainer />} />
</Route>
</Routes>
</Suspense>

View File

@@ -1,5 +1,6 @@
import {defaultsDeep} from "lodash";
import {theme} from "antd";
import InstanceRenderMgr from '../utils/instanceRenderMgr'
const {defaultAlgorithm, darkAlgorithm} = theme;
@@ -23,7 +24,10 @@ const defaultTheme = {
}
},
token: {
colorPrimary: '#1677ff'
colorPrimary: InstanceRenderMgr({
imex: "#1677ff",
rome:"#326ade"}) ,
colorInfo: "#326ade"
}
};
@@ -57,4 +61,4 @@ const finaltheme = {
algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm,
...defaultsDeep(currentTheme, defaultTheme)
}
export default finaltheme;
export default finaltheme;

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View File

@@ -1,4 +1,5 @@
import {useApolloClient, useMutation} from "@apollo/client";
import {useSplitTreatments} from "@splitsoftware/splitio-react";
import {Button, Checkbox, Form, Modal, notification, Space} from "antd";
import _ from "lodash";
import React, {useEffect, useMemo, useState} from "react";
@@ -57,9 +58,21 @@ function BillEnterModalContainer({
"enter_bill_generate_label",
false
);
const {treatments: {Enhanced_Payroll}} = useSplitTreatments({
attributes: {},
names: ["Enhanced_Payroll"],
splitKey: bodyshop.imexshopid,
});
const formValues = useMemo(() => {
return {
...billEnterModal.context.bill,
//Added as a part of IO-2436 for capturing parts price changes.
billlines: billEnterModal.context?.bill?.billlines?.map((line) => ({
...line,
original_actual_price: line.actual_price,
})),
jobid:
(billEnterModal.context.job && billEnterModal.context.job.id) || null,
federal_tax_rate:
@@ -92,6 +105,7 @@ function BillEnterModalContainer({
} = values;
let adjustmentsToInsert = {};
let payrollAdjustmentsToInsert = [];
const r1 = await insertBill({
variables: {
@@ -107,14 +121,33 @@ function BillEnterModalContainer({
lbr_adjustment,
location: lineLocation,
part_type,
create_ppc,
original_actual_price,
...restI
} = i;
if (deductedfromlbr) {
adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] =
(adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] || 0) -
restI.actual_price / lbr_adjustment.rate;
if (Enhanced_Payroll.treatment === "on") {
if (
deductedfromlbr &&
true //payroll is on
) {
payrollAdjustmentsToInsert.push({
id: i.joblineid,
convertedtolbr: true,
convertedtolbr_data: {
mod_lb_hrs: lbr_adjustment.mod_lb_hrs * -1,
mod_lbr_ty: lbr_adjustment.mod_lbr_ty,
},
});
}
} else {
if (deductedfromlbr) {
adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] =
(adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] || 0) -
restI.actual_price / lbr_adjustment.rate;
}
}
return {
...restI,
deductedfromlbr: deductedfromlbr,
@@ -140,6 +173,20 @@ function BillEnterModalContainer({
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID", "GET_JOB_BY_PK"],
});
await Promise.all(
payrollAdjustmentsToInsert.map((li) => {
return updateJobLines({
variables: {
lineId: li.id,
line: {
convertedtolbr: li.convertedtolbr,
convertedtolbr_data: li.convertedtolbr_data,
},
},
});
})
);
const adjKeys = Object.keys(adjustmentsToInsert);
if (adjKeys.length > 0) {
//Query the adjustments, merge, and update them.
@@ -248,6 +295,14 @@ function BillEnterModalContainer({
location: li.location || location,
status:
bodyshop.md_order_statuses.default_received || "Received*",
//Added parts price changes.
...(li.create_ppc &&
li.original_actual_price !== li.actual_price
? {
act_price_before_ppc: li.original_actual_price,
act_price: li.actual_price,
}
: {}),
},
},
});
@@ -316,12 +371,12 @@ function BillEnterModalContainer({
});
if (enterAgain) {
form.resetFields();
form.resetFields();
// form.resetFields();
form.setFieldsValue({
...formValues,
billlines: [],
});
form.resetFields();
} else {
toggleModalVisible();
}

View File

@@ -1,10 +1,6 @@
import React from "react";
import {
PlusCircleFilled,
MinusCircleFilled,
WarningOutlined,
} from "@ant-design/icons";
import {Form, Button, InputNumber, Input, Select, Switch, Space} from "antd";
import {MinusCircleFilled, PlusCircleFilled, WarningOutlined,} from "@ant-design/icons";
import {Button, Form, Input, InputNumber, Select, Space, Switch} from "antd";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";

View File

@@ -21,6 +21,7 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import BillFormLines from "./bill-form.lines.component";
import {CalculateBillTotal} from "./bill-form.totals.utility";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -68,17 +69,17 @@ export function BillFormComponent({
};
const handleFederalTaxExemptSwitchToggle = (checked) => {
// Early gate
if (!checked) return;
const values = form.getFieldsValue("billlines");
// Gate bill lines
if (!values?.billlines?.length) return;
// Early gate
if (!checked) return;
const values = form.getFieldsValue("billlines");
// Gate bill lines
if (!values?.billlines?.length) return;
const billlines = values.billlines.map((b) => {
b.applicable_taxes.federal = false;
return b;
});
form.setFieldsValue({billlines});
const billlines = values.billlines.map((b) => {
b.applicable_taxes.federal = false;
return b;
});
form.setFieldsValue({ billlines });
};
useEffect(() => {
@@ -368,13 +369,17 @@ export function BillFormComponent({
)}
</LayoutFormRow>
<LayoutFormRow>
<Form.Item
span={3}
label={t("bills.fields.federal_tax_rate")}
name="federal_tax_rate"
>
<CurrencyInput min={0} disabled={disabled}/>
</Form.Item>
{
InstanceRenderManager({imex:
<Form.Item
span={3}
label={t("bills.fields.federal_tax_rate")}
name="federal_tax_rate"
>
<CurrencyInput min={0} disabled={disabled} />
</Form.Item> })
}
<Form.Item
span={3}
label={t("bills.fields.state_tax_rate")}
@@ -382,22 +387,26 @@ export function BillFormComponent({
>
<CurrencyInput min={0} disabled={disabled}/>
</Form.Item>
<Form.Item
span={3}
label={t("bills.fields.local_tax_rate")}
name="local_tax_rate"
>
<CurrencyInput min={0}/>
</Form.Item>
{bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? (
<Form.Item
span={2}
label={t("bills.labels.federal_tax_exempt")}
name="federal_tax_exempt"
>
<Switch onChange={handleFederalTaxExemptSwitchToggle}/>
</Form.Item>
) : null}
{
InstanceRenderManager({imex: <>
<Form.Item
span={3}
label={t("bills.fields.local_tax_rate")}
name="local_tax_rate"
>
<CurrencyInput min={0} />
</Form.Item>
{bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? (
<Form.Item
span={2}
label={t("bills.labels.federal_tax_exempt")}
name="federal_tax_exempt"
>
<Switch onChange={handleFederalTaxExemptSwitchToggle} />
</Form.Item>
) : null}
</>})
}
<Form.Item shouldUpdate span={13}>
{() => {
const values = form.getFieldsValue([
@@ -423,21 +432,25 @@ export function BillFormComponent({
value={totals.subtotal.toFormat()}
precision={2}
/>
<Statistic
title={t("bills.labels.federal_tax")}
value={totals.federalTax.toFormat()}
precision={2}
/>
{
InstanceRenderManager({imex: <Statistic
title={t("bills.labels.federal_tax")}
value={totals.federalTax.toFormat()}
precision={2}
/> })
}
<Statistic
title={t("bills.labels.state_tax")}
value={totals.stateTax.toFormat()}
precision={2}
/>
<Statistic
title={t("bills.labels.local_tax")}
value={totals.localTax.toFormat()}
precision={2}
/>
{
InstanceRenderManager({imex: <Statistic
title={t("bills.labels.local_tax")}
value={totals.localTax.toFormat()}
precision={2}
/>})
}
<Statistic
title={t("bills.labels.entered_total")}
value={totals.enteredTotal.toFormat()}

View File

@@ -1,6 +1,6 @@
import {DeleteFilled, DollarCircleFilled} from "@ant-design/icons";
import {useSplitTreatments} from "@splitsoftware/splitio-react";
import {Button, Form, Input, InputNumber, Select, Space, Switch, Table, Tooltip,} from "antd";
import {Button, Checkbox, Form, Input, InputNumber, Select, Space, Switch, Table, Tooltip,} from "antd";
import React from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
@@ -10,6 +10,7 @@ import CiecaSelect from "../../utils/Ciecaselect";
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -32,9 +33,9 @@ export function BillEnterModalLinesComponent({
const {t} = useTranslation();
const {setFieldsValue, getFieldsValue, getFieldValue} = form;
const {treatments: {Simple_Inventory}} = useSplitTreatments({
const {treatments: {Simple_Inventory, Enhanced_Payroll}} = useSplitTreatments({
attributes: {},
names: ["Simple_Inventory"],
names: ["Simple_Inventory", "Enhanced_Payroll"],
splitKey: bodyshop && bodyshop.imexshopid,
});
@@ -87,6 +88,7 @@ export function BillEnterModalLinesComponent({
line_desc: opt.line_desc,
quantity: opt.part_qty || 1,
actual_price: opt.cost,
original_actual_price: opt.cost,
cost_center: opt.part_type
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
? opt.part_type !== "PAE"
@@ -213,6 +215,46 @@ export function BillEnterModalLinesComponent({
}}
/>
),
additional: (record, index) => (
InstanceRenderManager({rome: <Form.Item
dependencies={["billlines", record.name, "actual_price"]}
noStyle
>
{() => {
const billLine = getFieldValue(["billlines", record.name]);
const jobLine = lineData.find(
(line) => line.id === billLine?.joblineid
);
if (
!billEdit &&
billLine &&
jobLine &&
billLine?.actual_price !== jobLine?.act_price
) {
return (
<Space size="small">
<Form.Item
noStyle
label={t("joblines.fields.create_ppc")}
key={`${index}ppc`}
valuePropName="checked"
name={[record.name, "create_ppc"]}
>
<Checkbox/>
</Form.Item>
{t("joblines.fields.create_ppc")}
</Space>
);
} else {
return null;
}
}}
</Form.Item>
//Do not need to set for promanager as it will default to Rome.
})
),
},
{
title: t("billlines.fields.actual_cost"),
@@ -354,7 +396,7 @@ export function BillEnterModalLinesComponent({
},
formInput: (record, index) => <Switch disabled={disabled}/>,
additional: (record, index) => (
<Form.Item shouldUpdate style={{display: "inline-block"}}>
<Form.Item shouldUpdate noStyle style={{display: "inline-block"}}>
{() => {
const price = getFieldValue([
"billlines",
@@ -369,12 +411,34 @@ export function BillEnterModalLinesComponent({
"rate",
]);
const billline = getFieldValue(["billlines", record.name]);
const jobline = lineData.find(
(line) => line.id === billline?.joblineid
);
const employeeTeamName = bodyshop.employee_teams.find(
(team) => team.id === jobline?.assigned_team
);
if (getFieldValue(["billlines", record.name, "deductedfromlbr"]))
return (
<div>
{Enhanced_Payroll.treatment === "on" ?
<Space>
{t("joblines.fields.assigned_team", {
name: employeeTeamName?.name,
})}
{`${jobline.mod_lb_hrs} units/${t(
`joblines.fields.lbr_types.${jobline.mod_lbr_ty}`
)}`}
</Space> :
null}
<Form.Item
label={t("joblines.fields.mod_lbr_ty")}
key={`${index}modlbrty`}
initialValue={jobline ? jobline.mod_lbr_ty : null}
rules={[
{
required: true,
@@ -428,22 +492,44 @@ export function BillEnterModalLinesComponent({
</Select.Option>
</Select>
</Form.Item>
<Form.Item
label={t("jobs.labels.adjustmentrate")}
name={[record.name, "lbr_adjustment", "rate"]}
initialValue={bodyshop.default_adjustment_rate}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber precision={2} min={0.01}/>
</Form.Item>
{price &&
adjustmentRate &&
`${(price / adjustmentRate).toFixed(1)} hrs`}
{Enhanced_Payroll.treatment === "on" ? (
<Form.Item
label={t("billlines.labels.mod_lbr_adjustment")}
name={[record.name, "lbr_adjustment", "mod_lb_hrs"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber
precision={5}
min={0.01}
max={jobline ? jobline.mod_lb_hrs : 0}
/>
</Form.Item>
) : (
<Form.Item
label={t("jobs.labels.adjustmentrate")}
name={[record.name, "lbr_adjustment", "rate"]}
initialValue={bodyshop.default_adjustment_rate}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber precision={2} min={0.01}/>
</Form.Item>
)}
<Space>
{price &&
adjustmentRate &&
`${(price / adjustmentRate).toFixed(1)} hrs`}
</Space>
</div>
);
return <></>;
@@ -451,22 +537,24 @@ export function BillEnterModalLinesComponent({
</Form.Item>
),
},
{
title: t("billlines.fields.federal_tax_applicable"),
dataIndex: "applicable_taxes.federal",
editable: true,
formItemProps: (field) => {
return {
key: `${field.index}fedtax`,
valuePropName: "checked",
initialValue:
form.getFieldValue("federal_tax_exempt") === true ? false : true,
name: [field.name, "applicable_taxes", "federal"],
};
},
formInput: (record, index) => <Switch disabled={disabled}/>,
},
...InstanceRenderManager({imex: { title: t("billlines.fields.federal_tax_applicable"),
dataIndex: "applicable_taxes.federal",
editable: true,
formItemProps: (field) => {
return {
key: `${field.index}fedtax`,
valuePropName: "checked",
// initialValue: true,
name: [field.name, "applicable_taxes", "federal"],
};
},
formInput: (record, index) => <Switch disabled={disabled} />,}})
,
{
title: t("billlines.fields.state_tax_applicable"),
dataIndex: "applicable_taxes.state",
@@ -481,20 +569,22 @@ export function BillEnterModalLinesComponent({
},
formInput: (record, index) => <Switch disabled={disabled}/>,
},
{
...InstanceRenderManager({imex: {
title: t("billlines.fields.local_tax_applicable"),
dataIndex: "applicable_taxes.local",
editable: true,
formItemProps: (field) => {
return {
key: `${field.index}localtax`,
valuePropName: "checked",
name: [field.name, "applicable_taxes", "local"],
};
return {
key: `${field.index}localtax`,
valuePropName: "checked",
name: [field.name, "applicable_taxes", "local"],
};
},
formInput: (record, index) => <Switch disabled={disabled}/>,
},
formInput: (record, index) => <Switch disabled={disabled} />,
}})
,
{
title: t("general.labels.actions"),
@@ -619,7 +709,7 @@ const EditableCell = ({
if (additional)
return (
<td {...restProps}>
<Space size="small">
<div size="small">
<Form.Item
name={dataIndex}
labelCol={{span: 0}}
@@ -628,7 +718,7 @@ const EditableCell = ({
{(formInput && formInput(record, record.name)) || children}
</Form.Item>
{additional && additional(record, record.name)}
</Space>
</div>
</td>
);
if (wrapper)

View File

@@ -19,14 +19,14 @@ export const CalculateBillTotal = (invoice) => {
}).multiply(i.quantity || 1);
subtotal = subtotal.add(itemTotal);
if (i.applicable_taxes.federal) {
if (i.applicable_taxes?.federal) {
federalTax = federalTax.add(
itemTotal.percentage(federal_tax_rate || 0)
);
}
if (i.applicable_taxes.state)
if (i.applicable_taxes?.state)
stateTax = stateTax.add(itemTotal.percentage(state_tax_rate || 0));
if (i.applicable_taxes.local)
if (i.applicable_taxes?.local)
localTax = localTax.add(itemTotal.percentage(local_tax_rate || 0));
}
});

View File

@@ -1,6 +1,7 @@
import {Select} from "antd";
import React, {forwardRef} from "react";
import {useTranslation} from "react-i18next";
import InstanceRenderMgr from '../../utils/instanceRenderMgr';
//To be used as a form element only.
const {Option} = Select;
@@ -67,6 +68,22 @@ const BillLineSearchSelect = (
item.oem_partno ? ` - ${item.oem_partno}` : ""
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim()}
</span>
{
InstanceRenderMgr
(
{
rome: item.act_price === 0 && item.mod_lb_hrs > 0 && (
<span style={{float: "right", paddingleft: "1rem"}}>
{`${item.mod_lb_hrs} units`}
</span>
)
}
)
}
<span style={{float: "right", paddingleft: "1rem"}}>
{item.act_price
? `$${item.act_price && item.act_price.toFixed(2)}`

View File

@@ -4,11 +4,7 @@ import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
bodyshop: selectBodyshop,

View File

@@ -187,7 +187,7 @@ export const uploadToCloudinary = async (
message: JSON.stringify(documentInsert.errors),
}),
});
return;
return;
}
};

View File

@@ -6,14 +6,8 @@ import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {logImEXEvent} from "../../firebase/firebase.utils";
import {toggleEmailOverlayVisible} from "../../redux/email/email.actions";
import {
selectEmailConfig,
selectEmailVisible,
} from "../../redux/email/email.selectors.js";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import {selectEmailConfig, selectEmailVisible,} from "../../redux/email/email.selectors.js";
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
import RenderTemplate from "../../utils/RenderTemplate";
import {EmailSettings} from "../../utils/TemplateConstants";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";

View File

@@ -1,11 +1,11 @@
import {Select, Space, Tag} from "antd";
import React, {forwardRef} from "react";
import React from "react";
import {useTranslation} from "react-i18next";
const {Option} = Select;
//To be used as a form element only.
const EmployeeSearchSelect = ({options, ...props}, ref) => {
const EmployeeSearchSelect = ({options, ...props}) => {
const {t} = useTranslation();
return (
@@ -40,4 +40,4 @@ const EmployeeSearchSelect = ({options, ...props}, ref) => {
</Select>
);
};
export default forwardRef(EmployeeSearchSelect);
export default EmployeeSearchSelect;

View File

@@ -0,0 +1,33 @@
import {useQuery} from "@apollo/client";
import {Select} from "antd";
import React, {forwardRef} from "react";
import {QUERY_TEAMS} from "../../graphql/employee_teams.queries";
import AlertComponent from "../alert/alert.component";
//To be used as a form element only.
const EmployeeTeamSearchSelect = ({...props}, ref) => {
const {loading, error, data} = useQuery(QUERY_TEAMS);
if (error) return <AlertComponent message={JSON.stringify(error)}/>;
return (
<Select
showSearch
allowClear
loading={loading}
style={{
width: 400,
}}
options={
data
? data.employee_teams.map((e) => ({
value: JSON.stringify(e),
label: e.name,
}))
: []
}
{...props}
/>
);
};
export default forwardRef(EmployeeTeamSearchSelect);

View File

@@ -6,6 +6,7 @@ import * as Sentry from "@sentry/react";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
@@ -37,22 +38,24 @@ class ErrorBoundary extends React.Component {
}
handleErrorSubmit = () => {
window.$crisp.push([
"do",
"message:send",
[
"text",
`I hit the following error: \n\n
${this.state.error.message}\n\n
${this.state.error.stack}\n\n
URL:${window.location} as ${this.props.currentUser.email} for ${
InstanceRenderManager({executeFunction:true, imex: () => {
window.$crisp.push([
"do",
"message:send",
[
"text",
`I hit the following error: \n\n
${this.state.error.message}\n\n
${this.state.error.stack}\n\n
URL:${window.location} as ${this.props.currentUser.email} for ${
this.props.bodyshop && this.props.bodyshop.name
}
`,
],
]);
}
`,
],
]);
window.$crisp.push(["do", "chat:open"]);
window.$crisp.push(["do", "chat:open"]);
} })
// const errorDescription = `**Please add relevant details about what you were doing before you encountered this issue**
// ----

View File

@@ -1,9 +1,27 @@
import Dinero from "dinero.js";
import React, {forwardRef} from "react";
const ReadOnlyFormItem = ({value, type = "text", onChange}, ref) => {
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))
});
const ReadOnlyFormItem = (
{bodyshop, value, type = "text", onChange},
ref
) => {
if (!value) return null;
switch (type) {
case "employee":
const emp = bodyshop.employees.find((e) => e.id === value);
return `${emp?.first_name} ${emp?.last_name}`;
case "text":
return <div>{value}</div>;
case "currency":
@@ -14,4 +32,8 @@ const ReadOnlyFormItem = ({value, type = "text", onChange}, ref) => {
return <div>{value}</div>;
}
};
export default forwardRef(ReadOnlyFormItem);
export default connect(
mapStateToProps,
mapDispatchToProps
)(forwardRef(ReadOnlyFormItem));

View File

@@ -5,9 +5,7 @@ import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {Link, useNavigate} from "react-router-dom";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import OwnerNameDisplay, {
OwnerNameDisplayFunction,
} from "../owner-name-display/owner-name-display.component";
import OwnerNameDisplay, {OwnerNameDisplayFunction,} from "../owner-name-display/owner-name-display.component";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
export default function GlobalSearchOs() {

View File

@@ -7,9 +7,7 @@ import {Link, useNavigate} from "react-router-dom";
import {GLOBAL_SEARCH_QUERY} from "../../graphql/search.queries";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import AlertComponent from "../alert/alert.component";
import OwnerNameDisplay, {
OwnerNameDisplayFunction,
} from "../owner-name-display/owner-name-display.component";
import OwnerNameDisplay, {OwnerNameDisplayFunction,} from "../owner-name-display/owner-name-display.component";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
export default function GlobalSearch() {

View File

@@ -43,6 +43,7 @@ import {signOutStart} from "../../redux/user/user.actions";
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
import {FiLogOut} from "react-icons/fi";
import {checkBeta, handleBeta, setBeta} from "../../utils/betaHandler";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
@@ -174,8 +175,20 @@ function Header({
key: 'timetickets',
icon: <FieldTimeOutlined/>,
label: (<Link to="/manage/timetickets">{t("menus.header.timetickets")}</Link>)
},
{
});
if (bodyshop?.md_tasks_presets?.use_approvals) {
accountingChildren.push({
key: 'ttapprovals',
icon: <FieldTimeOutlined/>,
label:
<Link to="/manage/ttapprovals">
{t("menus.header.ttapprovals")}
</Link>
});
}
accountingChildren.push({
key: 'entertimetickets',
icon: <Icon component={GiPlayerTime}/>,
label: t("menus.header.entertimeticket"),
@@ -421,17 +434,19 @@ function Header({
icon: <Icon component={QuestionCircleFilled}/>,
label: t("menus.header.help"),
onClick: () => {
window.open("https://help.imex.online/", "_blank");
}
},
{
key: 'rescue',
icon: <Icon component={CarFilled}/>,
label: t("menus.header.rescueme"),
onClick: () => {
window.open("https://imexrescue.com/", "_blank");
window.open(InstanceRenderManager({imex:"https://help.imex.online/",rome: "https://rometech.com//", promanager:"https://web-est.com" })
, "_blank");
}
},
// {
// key: 'rescue',
// icon: <Icon component={CarFilled}/>,
// label: t("menus.header.rescueme"),
// onClick: () => {
// window.open("https://imexrescue.com/", "_blank");
// }
// },
{
key: 'shiftclock',
icon: <Icon component={GiPlayerTime}/>,
@@ -487,9 +502,9 @@ function Header({
key: 'beta-switch',
style: {marginLeft: 'auto'},
label: (
<Tooltip title="A more modern ImEX Online is ready for you to try! You can switch back at any time.">
<Tooltip title={`A more modern ${InstanceRenderManager({imex: 'ImEX Online', rome: "Rome Online"})} is ready for you to try! You can switch back at any time.`}>
<InfoCircleOutlined/>
<span style={{marginRight: 8}}>Try the new ImEX Online</span>
<span style={{marginRight: 8}}>Try the new app</span>
<Switch
checked={betaSwitch}
onChange={betaSwitchChange}

View File

@@ -1,27 +1,14 @@
import {AlertFilled} from "@ant-design/icons";
import {
Button,
Divider,
Dropdown,
Form,
Input,
notification,
Popover,
Select,
Space,
} from "antd";
import {Button, Divider, Dropdown, Form, Input, notification, Popover, Select, Space,} from "antd";
import parsePhoneNumber from "libphonenumber-js";
import dayjs from "../../utils/day";
import queryString from "query-string";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {Link, useNavigate, useLocation} from "react-router-dom";
import {Link, useLocation, useNavigate} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import {
openChatByPhone,
setMessage,
} from "../../redux/messaging/messaging.actions";
import {openChatByPhone, setMessage,} from "../../redux/messaging/messaging.actions";
import {setModalContext} from "../../redux/modals/modals.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";

View File

@@ -39,7 +39,8 @@ export default function JobBillsTotalComponent({
);
if (pol.cm_received === null) {
return; // Skip this calculation for bills posted prior to the CNR change.
return; //TODO:AIO This was previously removed. Check if functionality impacted.
// Skip this calculation for bills posted prior to the CNR change.
} else {
if (pol.cm_received === false) {
totalReturnsMarkedNotReceived = totalReturnsMarkedNotReceived.add(
@@ -87,7 +88,8 @@ export default function JobBillsTotalComponent({
const totalPartsSublet = Dinero(totals.parts.parts.total)
.add(Dinero(totals.parts.sublets.total))
.add(Dinero(totals.additional.shipping))
.add(Dinero(totals.additional.towing));
.add(Dinero(totals.additional.towing))
.add(Dinero(totals.additional.additionalCosts)); //TODO:AIO Additional costs were captured for Rome, but not imex. This may need to be evaluated?
const discrepancy = totalPartsSublet.subtract(billTotals);

View File

@@ -1,48 +1,53 @@
import {Button, notification} from "antd";
import Axios from "axios";
import React, {useState} from "react";
import {useMutation} from "@apollo/client";
import {useTranslation} from "react-i18next";
import {UPDATE_JOB} from "../../graphql/jobs.queries";
import Dinero from "dinero.js";
export default function JobCalculateTotals({job, disabled}) {
export default function JobCalculateTotals({job, disabled, refetch}) {
const {t} = useTranslation();
const [loading, setLoading] = useState(false);
const [updateJob] = useMutation(UPDATE_JOB);
const handleCalculate = async () => {
setLoading(true);
const newTotals = (
await Axios.post("/job/totals", {
job: job,
})
).data;
try {
setLoading(true);
const result = await updateJob({
refetchQueries: ["GET_JOB_BY_PK"],
awaitRefetchQueries: true,
variables: {
jobId: job.id,
job: {
job_totals: newTotals,
clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat(
"0.00"
),
},
},
});
if (!!!result.errors) {
notification["success"]({message: t("jobs.successes.updated")});
} else {
await Axios.post("/job/totalsssu", {
id: job.id,
});
if (refetch) refetch();
// const result = await updateJob({
// refetchQueries: ["GET_JOB_BY_PK"],
// awaitRefetchQueries: true,
// variables: {
// jobId: job.id,
// job: {
// job_totals: newTotals,
// clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
// owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat(
// "0.00"
// ),
// },
// },
// });
// if (!!!result.errors) {
// notification["success"]({ message: t("jobs.successes.updated") });
// } else {
// notification["error"]({
// message: t("jobs.errors.updating", {
// error: JSON.stringify(result.errors),
// }),
// });
// }
} catch (error) {
notification["error"]({
message: t("jobs.errors.updating", {
error: JSON.stringify(result.errors),
error: JSON.stringify(error),
}),
});
} finally {
setLoading(false);
}
setLoading(false);
};
return (

View File

@@ -8,17 +8,11 @@ import {connect} from "react-redux";
import {useLocation, useNavigate, useParams} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import {logImEXEvent} from "../../../../firebase/firebase.utils";
import {
MARK_APPOINTMENT_ARRIVED,
MARK_LATEST_APPOINTMENT_ARRIVED,
} from "../../../../graphql/appointments.queries";
import {MARK_APPOINTMENT_ARRIVED, MARK_LATEST_APPOINTMENT_ARRIVED,} from "../../../../graphql/appointments.queries";
import {UPDATE_JOB} from "../../../../graphql/jobs.queries";
import {UPDATE_OWNER} from "../../../../graphql/owners.queries";
import {insertAuditTrail} from "../../../../redux/application/application.actions";
import {
selectBodyshop,
selectCurrentUser,
} from "../../../../redux/user/user.selectors";
import {selectBodyshop, selectCurrentUser,} from "../../../../redux/user/user.selectors";
import AuditTrailMapping from "../../../../utils/AuditTrailMappings";
import ConfigFormComponents from "../../../config-form-components/config-form-components.component";
import DateTimePicker from "../../../form-date-time-picker/form-date-time-picker.component";

View File

@@ -4,12 +4,8 @@ import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {useParams} from "react-router-dom";
import {logImEXEvent} from "../../../../firebase/firebase.utils";
import {
GenerateDocument,
GenerateDocuments,
} from "../../../../utils/RenderTemplate";
import {GenerateDocument, GenerateDocuments,} from "../../../../utils/RenderTemplate";
import {TemplateList} from "../../../../utils/TemplateConstants";
const TemplateListGenerated = TemplateList();
export default function JobIntakeTemplateList({templates}) {

View File

@@ -7,9 +7,9 @@ import {connect} from "react-redux";
import {useNavigate} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import {UPDATE_JOB_LINES_IOU} from "../../graphql/jobs-lines.queries";
import {selectTechnician} from "../../redux/tech/tech.selectors";
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
import {CreateIouForJob} from "../jobs-detail-header-actions/jobs-detail-header-actions.duplicate.util";
import {selectTechnician} from "../../redux/tech/tech.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -22,13 +22,7 @@ const mapDispatchToProps = (dispatch) => ({
});
export default connect(mapStateToProps, mapDispatchToProps)(JobCreateIOU);
export function JobCreateIOU({
bodyshop,
currentUser,
job,
selectedJobLines,
technician,
}) {
export function JobCreateIOU({bodyshop, currentUser, job, selectedJobLines, technician}) {
const {t} = useTranslation();
const [loading, setLoading] = useState(false);
const client = useApolloClient();
@@ -88,19 +82,13 @@ export function JobCreateIOU({
title={t("jobs.labels.createiouwarning")}
onConfirm={handleCreateIou}
disabled={
!selectedJobLines ||
selectedJobLines.length === 0 ||
!job.converted ||
technician
!selectedJobLines || selectedJobLines.length === 0 || !job.converted || technician
}
>
<Button
loading={loading}
disabled={
!selectedJobLines ||
selectedJobLines.length === 0 ||
!job.converted ||
technician
!selectedJobLines || selectedJobLines.length === 0 || !job.converted || technician
}
>
{t("jobs.actions.createiou")}

View File

@@ -32,9 +32,9 @@ const mapDispatchToProps = (dispatch) => ({
});
const span = {
sm: {span: 24},
md: {span: 12},
lg: {span: 8},
lg: {span: 24},
xl: {span: 12},
xxl: {span: 8},
};
export function JobDetailCards({bodyshop, setPrintCenterContext}) {
@@ -137,12 +137,6 @@ export function JobDetailCards({bodyshop, setPrintCenterContext}) {
data={data ? data.jobs_by_pk : null}
/>
</Col>
<Col {...span}>
<JobDetailCardsPartsComponent
loading={loading}
data={data ? data.jobs_by_pk : null}
/>
</Col>
<Col {...span}>
<JobDetailCardsNotesComponent
loading={loading}
@@ -163,6 +157,12 @@ export function JobDetailCards({bodyshop, setPrintCenterContext}) {
data={data ? data.jobs_by_pk : null}
/>
</Col>
<Col span={24}>
<JobDetailCardsPartsComponent
loading={loading}
data={data ? data.jobs_by_pk : null}
/>
</Col>
</Row>
</Card>
) : null}

View File

@@ -1,16 +1,120 @@
import {Table} from "antd";
import React from "react";
import {useTranslation} from "react-i18next";
import JobLineNotePopup from "../job-line-note-popup/job-line-note-popup.component";
import PartsStatusPie from "../parts-status-pie/parts-status-pie.component";
import CardTemplate from "./job-detail-cards.template.component";
export default function JobDetailCardsPartsComponent({loading, data}) {
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectJobReadOnly} from "../../redux/application/application.selectors";
import {onlyUnique} from "../../utils/arrayHelper";
import {alphaSort} from "../../utils/sorters";
import JobLineLocationPopup from "../job-line-location-popup/job-line-location-popup.component";
import JobLineStatusPopup from "../job-line-status-popup/job-line-status-popup.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
jobRO: selectJobReadOnly,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobDetailCardsPartsComponent);
export function JobDetailCardsPartsComponent({loading, data, jobRO}) {
const {t} = useTranslation();
const {joblines_status} = data;
const columns = [
{
title: t("joblines.fields.line_desc"),
dataIndex: "line_desc",
fixed: "left",
key: "line_desc",
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
onCell: (record) => ({
className: record.manual_line && "job-line-manual",
style: {
...(record.critical ? {boxShadow: " -.5em 0 0 #FFC107"} : {}),
},
}),
width: "30%",
ellipsis: true,
},
{
title: t("joblines.fields.part_type"),
dataIndex: "part_type",
key: "part_type",
width: "15%",
sorter: (a, b) =>
alphaSort(
t(`joblines.fields.part_types.${a.part_type}`),
t(`joblines.fields.part_types.${b.part_type}`)
),
render: (text, record) =>
record.part_type
? t(`joblines.fields.part_types.${record.part_type}`)
: null,
},
{
title: t("joblines.fields.part_qty"),
dataIndex: "part_qty",
key: "part_qty",
width: "10%",
},
{
title: t("joblines.fields.notes"),
dataIndex: "notes",
key: "notes",
render: (text, record) => (
<JobLineNotePopup disabled={jobRO} jobline={record}/>
),
},
{
title: t("joblines.fields.location"),
dataIndex: "location",
key: "location",
sorter: (a, b) => alphaSort(a.location, b.location),
render: (text, record) => (
<JobLineLocationPopup jobline={record} disabled={jobRO}/>
),
},
{
title: t("joblines.fields.status"),
dataIndex: "status",
key: "status",
sorter: (a, b) => alphaSort(a.status, b.status),
filters:
(data &&
data.joblines
?.map((l) => l.status)
.filter(onlyUnique)
.map((s) => {
return {
text: s || "No Status*",
value: [s],
};
})) ||
[],
onFilter: (value, record) => value.includes(record.status),
render: (text, record) => (
<JobLineStatusPopup jobline={record} disabled={jobRO}/>
),
},
];
return (
<div>
<CardTemplate loading={loading} title={t("jobs.labels.cards.parts")}>
<PartsStatusPie joblines_status={joblines_status}/>
<Table
key="id"
columns={columns}
dataSource={data ? data.joblines : []}
/>
</CardTemplate>
</div>
);

View File

@@ -8,7 +8,19 @@ import CurrencyFormatter from "../../utils/CurrencyFormatter";
import {DateFormatter} from "../../utils/DateFormatter";
import AlertComponent from "../alert/alert.component";
export default function JobLinesExpander({jobline, jobid}) {
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)(JobLinesExpander);
export function JobLinesExpander({jobline, jobid, bodyshop}) {
const {t} = useTranslation();
const {loading, error, data} = useQuery(GET_JOB_LINE_ORDERS, {
fetchPolicy: "network-only",
@@ -23,7 +35,7 @@ export default function JobLinesExpander({jobline, jobid}) {
return (
<Row>
<Col md={24} lg={12}>
<Col md={24} lg={8}>
<Typography.Title level={4}>
{t("parts_orders.labels.parts_orders")}
</Typography.Title>
@@ -52,7 +64,7 @@ export default function JobLinesExpander({jobline, jobid}) {
]
}
/> </Col>
<Col md={24} lg={12}>
<Col md={24} lg={8}>
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
<Timeline
items={
@@ -90,6 +102,37 @@ export default function JobLinesExpander({jobline, jobid}) {
}
/>
</Col>
<Col md={24} lg={8}>
<Typography.Title level={4}>
{t("parts_dispatch.labels.parts_dispatch")}
</Typography.Title>
<Timeline items={
data.parts_dispatch_lines.length > 0 ? (
data.parts_dispatch_lines.map((line) => ({
key: line.id,
children: (
<Space split={<Divider type="vertical"/>} wrap>
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.id}`}>
{line.parts_dispatch.number}
</Link>
{
bodyshop.employees.find(
(e) => e.id === line.parts_dispatch.employeeid
)?.first_name
}
<Space>
{t("parts_dispatch_lines.fields.accepted_at")}
<DateFormatter>{line.accepted_at}</DateFormatter>
</Space>
</Space>
)
}))
) : ({
key: 'dispatch-lines',
children: t("parts_orders.labels.notyetordered"),
})
}/>
</Col>
</Row>
);
}

View File

@@ -0,0 +1,99 @@
import {useMutation} from "@apollo/client";
import {Button, Form, notification, Popover, Tooltip} from "antd";
import {t} from "i18next";
import React, {useState} from "react";
import {UPDATE_LINE_PPC} from "../../graphql/jobs-lines.queries";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component";
import axios from "axios";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
export default function JobLinesPartPriceChange({job, line, refetch}) {
const [loading, setLoading] = useState(false);
const [updatePartPrice] = useMutation(UPDATE_LINE_PPC);
const handleFinish = async (values) => {
try {
setLoading(true);
const result = await updatePartPrice({
variables: {
id: line.id,
jobline: {
act_price_before_ppc: line.act_price_before_ppc
? line.act_price_before_ppc
: line.act_price,
act_price: values.act_price,
},
},
});
await axios.post("/job/totalsssu", {
id: job.id,
});
if (result.errors) {
notification.open({
type: "error",
message: t("joblines.errors.saving", {
error: JSON.stringify(result.errors),
}),
});
if (refetch) refetch();
} else {
notification.open({
type: "success",
message: t("joblines.successes.saved"),
});
}
} catch (error) {
notification.open({
type: "error",
message: t("joblines.errors.saving", {error: JSON.stringify(error)}),
});
} finally {
setLoading(false);
}
};
const popcontent = (
<Form layout="vertical" onFinish={handleFinish} initialValues={{act_price: line.act_price}}>
<Form.Item
name="act_price"
label={t("jobs.labels.act_price_ppc")}
rules={[{required: true}]}
>
<CurrencyFormItemComponent/>
</Form.Item>
<Button loading={loading} htmlType="primary">
{t("general.actions.save")}
</Button>
</Form>
);
return (
<JobLineConvertToLabor jobline={line} job={job}>
<Popover trigger="click" disabled={line.manual_line || InstanceRenderManager({imex:false, rome:true})} content={popcontent}>
<CurrencyFormatter>
{line.db_ref === "900510" || line.db_ref === "900511"
? line.prt_dsmk_m
: line.act_price}
</CurrencyFormatter>
{line.prt_dsmk_p && line.prt_dsmk_p !== 0 ? (
<span style={{marginLeft: ".2rem"}}>{`(${line.prt_dsmk_p}%)`}</span>
) : (
<></>
)}
{line.act_price_before_ppc && line.act_price_before_ppc !== 0 ? (
<Tooltip title={t("jobs.labels.ppc")}>
<span style={{marginLeft: ".2rem", color: "tomato"}}>
(
<CurrencyFormatter>{line.act_price_before_ppc}</CurrencyFormatter>
)
</span>
</Tooltip>
) : (
<></>
)}
</Popover>
</JobLineConvertToLabor>
);
}

View File

@@ -21,7 +21,6 @@ import {selectJobReadOnly} from "../../redux/application/application.selectors";
import {setModalContext} from "../../redux/modals/modals.actions";
import {selectTechnician} from "../../redux/tech/tech.selectors";
import {onlyUnique} from "../../utils/arrayHelper";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import {alphaSort} from "../../utils/sorters";
import JobLineLocationPopup from "../job-line-location-popup/job-line-location-popup.component";
import JobLineNotePopup from "../job-line-note-popup/job-line-note-popup.component";
@@ -30,13 +29,18 @@ import JobLinesBillRefernece from "../job-lines-bill-reference/job-lines-bill-re
// import AllocationsAssignmentContainer from "../allocations-assignment/allocations-assignment.container";
// import AllocationsBulkAssignmentContainer from "../allocations-bulk-assignment/allocations-bulk-assignment.container";
// import AllocationsEmployeeLabelContainer from "../allocations-employee-label/allocations-employee-label.container";
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
import _ from "lodash";
import JobCreateIOU from "../job-create-iou/job-create-iou.component";
import JobSendPartPriceChangeComponent from "../job-send-parts-price-change/job-send-parts-price-change.component";
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
import JobLinesExpander from "./job-lines-expander.component";
import {selectBodyshop} from "../../redux/user/user.selectors";
import dayjs from "../../utils/day";
import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component";
import JobLinesPartPriceChange from "./job-lines-part-price-change.component";
import JoblineTeamAssignment from "../job-line-team-assignment/job-line-team-assignmnent.component";
import JobLineDispatchButton from "../job-line-dispatch-button/job-line-dispatch-button.component";
import JobLineBulkAssignComponent from "../job-line-bulk-assign/job-line-bulk-assign.component";
import {useSplitTreatments} from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -68,6 +72,11 @@ export function JobLinesComponent({
setBillEnterContext,
}) {
const [deleteJobLine] = useMutation(DELETE_JOB_LINE_BY_PK);
const {treatments: {Enhanced_Payroll}} = useSplitTreatments({
attributes: {},
names: ["Enhanced_Payroll"],
splitKey: bodyshop.imexshopid,
});
const [selectedLines, setSelectedLines] = useState([]);
const [state, setState] = useState({
@@ -113,10 +122,21 @@ export function JobLinesComponent({
sortOrder:
state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order,
ellipsis: true,
render: (text, record) =>
`${record.oem_partno || ""} ${
record.alt_partno ? `(${record.alt_partno})` : ""
}`.trim(),
onCell: (record) => ({
className: record.manual_line && "job-line-manual",
style: {
...(record.parts_dispatch_lines[0]?.accepted_at
? {boxShadow: " -.5em 0 0 #FFC107"}
: {}),
},
}),
render: (text, record) => (
<span class="ant-table-cell-content">
{`${record.oem_partno || ""} ${
record.alt_partno ? `(${record.alt_partno})` : ""
}`.trim()}
</span>
),
},
{
title: t("joblines.fields.op_code_desc"),
@@ -212,20 +232,7 @@ export function JobLinesComponent({
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
ellipsis: true,
render: (text, record) => (
<JobLineConvertToLabor jobline={record} job={job}>
<CurrencyFormatter>
{record.db_ref === "900510" || record.db_ref === "900511"
? record.prt_dsmk_m
: record.act_price}
</CurrencyFormatter>
{record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? (
<span
style={{marginLeft: ".2rem"}}
>{`(${record.prt_dsmk_p}%)`}</span>
) : (
<></>
)}
</JobLineConvertToLabor>
<JobLinesPartPriceChange line={record} job={job} refetch={refetch}/>
),
},
{
@@ -278,6 +285,23 @@ export function JobLinesComponent({
state.sortedInfo.columnKey === "line_ind" && state.sortedInfo.order,
responsive: ["md"],
},
...(Enhanced_Payroll.treatment === "on"
? [
{
title: t("joblines.fields.assigned_team"),
dataIndex: "assigned_team",
key: "assigned_team",
render: (text, record) => (
<JoblineTeamAssignment
disabled={jobRO}
jobline={record}
jobId={job.id}
/>
),
},
]
: []),
{
title: t("joblines.fields.notes"),
dataIndex: "notes",
@@ -396,7 +420,11 @@ export function JobLinesComponent({
setSelectedLines((selectedLines) =>
_.uniq([
...selectedLines,
...jobLines.filter((item) => markedTypes.includes(item.part_type)),
...jobLines.filter(
(item) =>
markedTypes.includes(item.part_type) ||
markedTypes.includes(item.mod_lbr_ty)
),
])
);
}
@@ -410,6 +438,21 @@ export function JobLinesComponent({
{key: "PAL", label: t("joblines.fields.part_types.PAL")},
{key: "PAS", label: t("joblines.fields.part_types.PAS")},
{type: 'divider'},
{key: "LAA", label: t("joblines.fields.lbr_types.LAA")},
{key: "LAB", label: t("joblines.fields.lbr_types.LAB")},
{key: "LAD", label: t("joblines.fields.part_types.LAD")},
{key: "LAE", label: t("joblines.fields.part_types.LAE")},
{key: "LAF", label: t("joblines.fields.part_types.LAF")},
{key: "LAG", label: t("joblines.fields.part_types.LAG")},
{key: "LAM", label: t("joblines.fields.part_types.LAM")},
{key: "LAR", label: t("joblines.fields.part_types.LAR")},
{key: "LAS", label: t("joblines.fields.part_types.LAS")},
{key: "LAU", label: t("joblines.fields.part_types.LAU")},
{key: "LA1", label: t("joblines.fields.part_types.LA1")},
{key: "LA2", label: t("joblines.fields.part_types.LA2")},
{key: "LA3", label: t("joblines.fields.part_types.LA3")},
{key: "LA4", label: t("joblines.fields.part_types.LA4")},
{type: 'divider'},
{key: "clear", label: t("general.labels.clear")},
]
};
@@ -433,6 +476,18 @@ export function JobLinesComponent({
</Space>
</Tag>
)}
<JobLineDispatchButton
selectedLines={selectedLines}
setSelectedLines={setSelectedLines}
job={job}
/>
{Enhanced_Payroll.treatment === "on" && (
<JobLineBulkAssignComponent
selectedLines={selectedLines}
setSelectedLines={setSelectedLines}
job={job}
/>
)}
<Button
disabled={
(job && !job.converted) ||
@@ -441,15 +496,6 @@ export function JobLinesComponent({
technician
}
onClick={() => {
// setPartsOrderContext({
// actions: { refetch: refetch },
// context: {
// jobId: job.id,
// job: job,
// linesToOrder: selectedLines,
// },
// });
setBillEnterContext({
actions: {refetch: refetch},
context: {
@@ -556,6 +602,9 @@ export function JobLinesComponent({
>
{t("joblines.actions.new")}
</Button>
{bodyshop.region_config.toLowerCase().startsWith("us") && (
<JobSendPartPriceChangeComponent job={job}/>
)}
<JobCreateIOU job={job} selectedJobLines={selectedLines}/>
<Input.Search
placeholder={t("general.labels.search")}

View File

@@ -0,0 +1,147 @@
import React, {useState} from "react";
import {useMutation} from "@apollo/client";
import {Button, Form, notification, Popover, Select, Space} from "antd";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {UPDATE_LINE_BULK_ASSIGN} from "../../graphql/jobs-lines.queries";
import {selectJobReadOnly} from "../../redux/application/application.selectors";
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
import {insertAuditTrail} from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly,
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({jobid, operation}) =>
dispatch(insertAuditTrail({jobid, operation})),
});
export default connect(mapStateToProps, mapDispatchToProps)(JoblineBulkAssign);
export function JoblineBulkAssign({
setSelectedLines,
selectedLines,
insertAuditTrail,
bodyshop,
jobRO,
job,
currentUser,
}) {
const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();
const {t} = useTranslation();
const [assignLines] = useMutation(UPDATE_LINE_BULK_ASSIGN);
const handleConvert = async (values) => {
try {
setLoading(true);
const result = await assignLines({
variables: {
jobline: {
assigned_team: values.assigned_team,
},
ids: selectedLines.map((l) => l.id),
},
});
if (result.errors) {
notification.open({
type: "error",
message: t("parts_dispatch.errors.creating", {
error: JSON.stringify(result.errors),
}),
});
} else {
//Insert the audit trail here.
const teamName = bodyshop.employee_teams.find(
(et) => et.id === values.assigned_team
)?.name;
const hours = selectedLines.reduce(
(acc, val) => (acc += val.mod_lb_hrs),
0
);
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.assignedlinehours(
teamName,
hours.toFixed(1)
),
});
setSelectedLines([]);
setVisible(false);
}
} catch (error) {
notification.open({
type: "error",
message: t("parts_dispatch.errors.creating", {
error: error,
}),
});
} finally {
setLoading(false);
}
};
const popMenu = (
<div>
<Form layout="vertical" form={form} onFinish={handleConvert}>
<Form.Item
name={"assigned_team"}
label={t("joblines.fields.assigned_team")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select
showSearch
style={{width: 200}}
optionFilterProp="children"
filterOption={(input, option) =>
option.props.children
.toLowerCase()
.indexOf(input.toLowerCase()) >= 0
}
>
{bodyshop.employee_teams.map((team) => (
<Select.Option value={team.id} key={team.id} name={team.name}>
{team.name}
</Select.Option>
))}
</Select>
</Form.Item>
<Space wrap>
<Button type="danger" onClick={() => form.submit()} loading={loading}>
{t("general.actions.save")}
</Button>
<Button onClick={() => setVisible(false)}>
{t("general.actions.cancel")}
</Button>
</Space>
</Form>
</div>
);
return (
<Popover open={visible} content={popMenu}>
<Button
disabled={selectedLines.length === 0 || jobRO}
loading={loading}
onClick={() => setVisible(true)}
>
{t("joblines.actions.assign_team", {count: selectedLines.length})}
</Button>
</Popover>
);
}

View File

@@ -0,0 +1,165 @@
import React, {useState} from "react";
import {useMutation} from "@apollo/client";
import {Button, Form, notification, Popover, Select, Space} from "antd";
import day from "../../utils/day";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {INSERT_PARTS_DISPATCH} from "../../graphql/parts-dispatch.queries";
import {selectJobReadOnly} from "../../redux/application/application.selectors";
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
import {GenerateDocument} from "../../utils/RenderTemplate";
import {TemplateList} from "../../utils/TemplateConstants";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly,
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobLineDispatchButton);
export function JobLineDispatchButton({
setSelectedLines,
selectedLines,
bodyshop,
jobRO,
job,
currentUser,
}) {
const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();
const Templates = TemplateList("job_special", {
ro_number: job.ro_number,
});
const {t} = useTranslation();
const [dispatchLines] = useMutation(INSERT_PARTS_DISPATCH);
const handleConvert = async (values) => {
try {
setLoading(true);
//THIS HAS NOT YET BEEN TESTED. START BY FINISHING THIS FUNCTION.
const result = await dispatchLines({
variables: {
partsDispatch: {
dispatched_at: day(),
employeeid: values.employeeid,
jobid: job.id,
dispatched_by: currentUser.email,
parts_dispatch_lines: {
data: selectedLines.map((l) => ({
joblineid: l.id,
quantity: l.part_qty,
})),
},
},
//joblineids: selectedLines.map((l) => l.id),
},
});
if (result.errors) {
notification.open({
type: "error",
message: t("parts_dispatch.errors.creating", {
error: result.errors,
}),
});
} else {
setSelectedLines([]);
await GenerateDocument(
{
name: Templates.parts_dispatch.key,
variables: {
id: result.data.insert_parts_dispatch_one.id,
},
},
{},
"p"
);
}
setVisible(false);
} catch (error) {
notification.open({
type: "error",
message: t("parts_dispatch.errors.creating", {
error: error,
}),
});
} finally {
setLoading(false);
}
};
const popMenu = (
<div>
<Form layout="vertical" form={form} onFinish={handleConvert}>
<Form.Item
name={"employeeid"}
label={t("timetickets.fields.employee")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select
showSearch
style={{width: 200}}
optionFilterProp="children"
filterOption={(input, option) =>
option.props.children
.toLowerCase()
.indexOf(input.toLowerCase()) >= 0
}
>
{bodyshop.employees
.filter((emp) => emp.active)
.map((emp) => (
<Select.Option
value={emp.id}
key={emp.id}
name={`${emp.first_name} ${emp.last_name}`}
>
{`${emp.first_name} ${emp.last_name}`}
</Select.Option>
))}
</Select>
</Form.Item>
<Space wrap>
<Button
type="danger"
onClick={() => form.submit()}
loading={loading}
disabled={selectedLines.length === 0}
>
{t("general.actions.save")}
</Button>
<Button onClick={() => setVisible(false)}>
{t("general.actions.cancel")}
</Button>
</Space>
</Form>
</div>
);
return (
<Popover open={visible} content={popMenu}>
<Button
disabled={selectedLines.length === 0 || jobRO}
loading={loading}
onClick={() => setVisible(true)}
>
{t("joblines.actions.dispatchparts", {count: selectedLines.length})}
</Button>
</Popover>
);
}

View File

@@ -1,5 +1,5 @@
import {useMutation} from "@apollo/client";
import {notification, Select} from "antd";
import {notification, Select, Space} from "antd";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
@@ -77,7 +77,10 @@ export function JobLineLocationPopup({bodyshop, jobline, disabled}) {
style={{width: "100%", minHeight: "2rem", cursor: "pointer"}}
onClick={() => !disabled && setEditing(true)}
>
{jobline.location}
<Space wrap>
{jobline.location}
{jobline.parts_dispatch_lines?.length > 0 && "-Disp"}
</Space>
</div>
);
}

View File

@@ -0,0 +1,116 @@
import {notification, Select} from "antd";
import React, {useEffect, useState} from "react";
import {useMutation} from "@apollo/client";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {UPDATE_JOB_LINE} from "../../graphql/jobs-lines.queries";
import {selectBodyshop} from "../../redux/user/user.selectors";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import {insertAuditTrail} from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({jobid, operation}) =>
dispatch(insertAuditTrail({jobid, operation})),
});
export function JoblineTeamAssignment({
bodyshop,
jobline,
disabled,
jobId,
insertAuditTrail,
}) {
const [editing, setEditing] = useState(false);
const [loading, setLoading] = useState(false);
const [assignedTeam, setAssignedTeam] = useState(jobline.assigned_team);
const [updateJob] = useMutation(UPDATE_JOB_LINE);
const {t} = useTranslation();
useEffect(() => {
if (editing) setAssignedTeam(jobline.assigned_team);
}, [editing, jobline.assigned_team]);
const handleChange = (e) => {
setAssignedTeam(e);
};
const handleSave = async (e) => {
setLoading(true);
const result = await updateJob({
variables: {
lineId: jobline.id,
line: {assigned_team: assignedTeam},
},
});
if (!!!result.errors) {
notification["success"]({message: t("joblines.successes.saved")});
//insert the audit trail here.
const teamName = bodyshop.employee_teams.find(
(et) => et.id === assignedTeam
)?.name;
insertAuditTrail({
jobid: jobId,
operation: AuditTrailMapping.assignedlinehours(
teamName,
jobline.mod_lb_hrs
),
});
} else {
notification["error"]({
message: t("joblines.errors.saving", {
error: JSON.stringify(result.errors),
}),
});
}
setLoading(false);
setEditing(false);
};
if (editing)
return (
<div>
<LoadingSpinner loading={loading}>
<Select
autoFocus
allowClear
dropdownMatchSelectWidth={100}
value={assignedTeam}
onSelect={handleChange}
onBlur={handleSave}
onClear={() => handleChange(null)}
>
{Object.values(bodyshop.employee_teams).map((s, idx) => (
<Select.Option key={idx} value={s.id}>
{s.name}
</Select.Option>
))}
</Select>
</LoadingSpinner>
</div>
);
const team = bodyshop.employee_teams.find(
(tm) => tm.id === jobline.assigned_team
);
return (
<div
style={{width: "100%", minHeight: "1rem", cursor: "pointer"}}
onClick={() => !disabled && setEditing(true)}
>
{team?.name}
</div>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(JoblineTeamAssignment);

View File

@@ -0,0 +1,18 @@
import {Alert} from "antd";
import React from "react";
import {useTranslation} from "react-i18next";
export default function JobProfileDataWarning({job}) {
const {t} = useTranslation();
let missingProfileInfo =
Object.keys(job.cieca_pft).length === 0 ||
Object.keys(job.cieca_pfl).length === 0 ||
Object.keys(job.materials).length === 0;
if (missingProfileInfo)
return (
<Alert type="error" message={t("jobs.labels.missingprofileinfo")}></Alert>
);
return null;
}

View File

@@ -3,6 +3,7 @@ import React, {useState} from "react";
import JobReconciliationBillsTable from "../job-reconciliation-bills-table/job-reconciliation-bills-table.component";
import JobReconciliationPartsTable from "../job-reconciliation-parts-table/job-reconciliation-parts-table.component";
import JobReconciliationTotals from "../job-reconciliation-totals/job-reconciliation-totals.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
export default function JobReconciliationModalComponent({job, bills}) {
const jobLineState = useState([]);
@@ -17,14 +18,25 @@ export default function JobReconciliationModalComponent({job, bills}) {
)
.flat() || [];
const jobLineData = job.joblines.filter(
(j) =>
(j.part_type !== null && j.part_type !== "PAE") ||
(j.line_desc &&
j.line_desc.toLowerCase().includes("towing") &&
j.lbr_op === "OP13") ||
j.db_ref === "936004" //ADD SHIPPING LINE.
);
const filterFunction = InstanceRenderManager({
imex: (j) =>
(j.part_type !== null && j.part_type !== "PAE") ||
(j.line_desc &&
j.line_desc.toLowerCase().includes("towing") &&
j.lbr_op === "OP13") ||
j.db_ref === "936004", //ADD SHIPPING LINE.
rome: (j) =>
(j.part_type !== "PAE" &&
j.act_price !== 0 &&
j.part_qty !== 0) ||
j.misc_amt !== 0 ||
(j.line_desc &&
j.line_desc.toLowerCase().includes("towing") &&
j.lbr_op === "OP13") ||
j.db_ref === "936004", //ADD SHIPPING LINE.
});
const jobLineData = job.joblines.filter((j) => filterFunction(j));
return (
<div style={{flex: 1, display: "flex", flexDirection: "column"}}>

View File

@@ -0,0 +1,31 @@
import {Button, notification} from "antd";
import axios from "axios";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
export default function JobSendPartPriceChangeComponent({job}) {
const {t} = useTranslation();
const [loading, setLoading] = useState(false);
const handleClick = async () => {
setLoading(true);
try {
const ppcData = await axios.post("/job/ppc", {jobid: job.id});
await axios.post("http://localhost:1337/ppc/", ppcData.data);
} catch (error) {
notification.open({
type: "error",
message: t("jobs.errors.partspricechange", {
error: JSON.stringify(error),
}),
});
} finally {
setLoading(false);
}
};
return (
<Button onClick={handleClick} loading={loading}>
{t("jobs.actions.sendpartspricechange")}
</Button>
);
}

View File

@@ -67,7 +67,8 @@ export function JobsTotalsTableComponent({jobRO, currentUser, job}) {
<JobTotalsTableTotals job={job}/>
</Card>
</Col>
{currentUser.email.includes("@imex.") && (
{(currentUser.email.includes("@imex.") ||
currentUser.email.includes("@rome.")) && (
<Col span={24}>
<Card title="DEVELOPMENT USE ONLY">
<JobCalculateTotals job={job} disabled={jobRO}/>

View File

@@ -4,6 +4,7 @@ import React, {useMemo, useState} from "react";
import {useTranslation} from "react-i18next";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import {alphaSort} from "../../utils/sorters";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
export default function JobTotalsTableLabor({job}) {
const {t} = useTranslation();
@@ -103,9 +104,11 @@ export default function JobTotalsTableLabor({job}) {
<>
<Table.Summary.Row>
<Table.Summary.Cell>
<strong>{t("jobs.labels.labor_rates_subtotal")}</strong>
<strong>
{t("jobs.labels.labor_rates_subtotal")}
</strong>
</Table.Summary.Cell>
<Table.Summary.Cell/>
<Table.Summary.Cell />
<Table.Summary.Cell>
{(
job.job_totals.rates.mapa.hours +
@@ -114,7 +117,9 @@ export default function JobTotalsTableLabor({job}) {
</Table.Summary.Cell>
<Table.Summary.Cell align="right">
<strong>
{Dinero(job.job_totals.rates.rates_subtotal).toFormat()}
{Dinero(
job.job_totals.rates.rates_subtotal
).toFormat()}
</strong>
</Table.Summary.Cell>
</Table.Summary.Row>
@@ -122,13 +127,26 @@ export default function JobTotalsTableLabor({job}) {
<Table.Summary.Cell>
<Space>
{t("jobs.labels.mapa")}
{job.materials &&
job.materials.mapa &&
job.materials.mapa.cal_maxdlr &&
job.materials.mapa.cal_maxdlr > 0 &&
t("jobs.labels.threshhold", {
amount: job.materials.mapa.cal_maxdlr,
})}
{InstanceRenderManager({
imex:
job.materials &&
job.materials.mapa &&
job.materials.mapa.cal_maxdlr &&
job.materials.mapa.cal_maxdlr > 0 &&
t("jobs.labels.threshhold", {
amount: job.materials.mapa
.cal_maxdlr,
}),
rome:
job.materials &&
job.materials.MAPA &&
job.materials.MAPA.cal_maxdlr !==
undefined &&
t("jobs.labels.threshhold", {
amount: job.materials.MAPA
.cal_maxdlr,
}),
})}
</Space>
</Table.Summary.Cell>
<Table.Summary.Cell align="right">
@@ -147,13 +165,24 @@ export default function JobTotalsTableLabor({job}) {
<Table.Summary.Cell>
<Space wrap>
{t("jobs.labels.mash")}
{job.materials &&
job.materials.mash &&
job.materials.mash.cal_maxdlr &&
job.materials.mash.cal_maxdlr > 0 &&
t("jobs.labels.threshhold", {
amount: job.materials.mash.cal_maxdlr,
})}
{
InstanceRenderManager({
imex:job.materials &&
job.materials.mash &&
job.materials.mash.cal_maxdlr &&
job.materials.mash.cal_maxdlr > 0 &&
t("jobs.labels.threshhold", {
amount: job.materials.mash.cal_maxdlr,
}),
rome: job.materials &&
job.materials.MASH &&
job.materials.MASH.cal_maxdlr !==
undefined &&
t("jobs.labels.threshhold", {
amount: job.materials.MASH.cal_maxdlr,
})
})
}
</Space>
</Table.Summary.Cell>
<Table.Summary.Cell align="right">
@@ -172,11 +201,13 @@ export default function JobTotalsTableLabor({job}) {
<Table.Summary.Cell>
<strong>{t("jobs.labels.rates_subtotal")}</strong>
</Table.Summary.Cell>
<Table.Summary.Cell/>
<Table.Summary.Cell/>
<Table.Summary.Cell />
<Table.Summary.Cell />
<Table.Summary.Cell align="right">
<strong>
{Dinero(job.job_totals.rates.subtotal).toFormat()}
{Dinero(
job.job_totals.rates.subtotal
).toFormat()}
</strong>
</Table.Summary.Cell>
</Table.Summary.Row>

View File

@@ -11,6 +11,22 @@ export default function JobTotalsTableParts({job}) {
filteredInfo: {},
});
const insuranceAdjustments = useMemo(() => {
if (!job.job_totals) return [];
if (!job.job_totals?.parts?.adjustments) return [];
const adjs = [];
Object.keys(job.job_totals?.parts?.adjustments).forEach((key) => {
if (Dinero(job.job_totals?.parts?.adjustments[key]).getAmount() !== 0) {
adjs.push({
id: key,
amount: Dinero(job.job_totals.parts.adjustments[key]),
});
}
});
return adjs;
}, [job.job_totals]);
const data = useMemo(() => {
return Object.keys(job.job_totals.parts.parts.list)
.filter(
@@ -74,11 +90,11 @@ export default function JobTotalsTableParts({job}) {
<Table.Summary.Cell>
{t("jobs.labels.prt_dsmk_total")}
</Table.Summary.Cell>
<Table.Summary.Cell align="right">
{Dinero(job.job_totals.parts.parts.prt_dsmk_total).toFormat()}
</Table.Summary.Cell>
</Table.Summary.Row>
<Table.Summary.Row>
<Table.Summary.Cell>
<strong>{t("jobs.labels.partstotal")}</strong>
@@ -90,6 +106,26 @@ export default function JobTotalsTableParts({job}) {
</strong>
</Table.Summary.Cell>
</Table.Summary.Row>
{
//TODO:AIO This shoudl only be in the US version. need to verify whether this causes problems for the CA version.
insuranceAdjustments.length > 0 && (
<Table.Summary.Row>
<Table.Summary.Cell colSpan={24}>
{t("jobs.labels.profileadjustments")}
</Table.Summary.Cell>
</Table.Summary.Row>
)}
{insuranceAdjustments.map((adj, idx) => (
<Table.Summary.Row key={idx}>
<Table.Summary.Cell>
{t(`jobs.fields.${adj.id.toLowerCase()}`)}
</Table.Summary.Cell>
<Table.Summary.Cell align="right">
{adj.amount.toFormat()}
</Table.Summary.Cell>
</Table.Summary.Row>
))}
</>
)}
/>

View File

@@ -29,26 +29,109 @@ export function JobTotalsTableTotals({bodyshop, job}) {
total: job.job_totals.totals.subtotal,
bold: true,
},
{
key: t("jobs.labels.local_tax_amt"),
total: job.job_totals.totals.local_tax,
},
{
key: t("jobs.labels.state_tax_amt"),
total: job.job_totals.totals.state_tax,
},
...(bodyshop.region_config === "CA_BC"
...(job.job_totals.totals.us_sales_tax_breakdown
? [
{
key: t("jobs.fields.ca_bc_pvrt"),
total: job.job_totals.additional.pvrt,
key: `${
bodyshop.md_responsibility_centers.taxes.tax_ty1?.tax_type1 ||
"T1"
} - ${[
job.cieca_pft.ty1_rate1,
job.cieca_pft.ty1_rate2,
job.cieca_pft.ty1_rate3,
job.cieca_pft.ty1_rate4,
job.cieca_pft.ty1_rate5,
]
.filter((i) => i > 0)
.join(", ")}%`,
total: job.job_totals.totals.us_sales_tax_breakdown.ty1Tax,
},
]
: []),
{
key: t("jobs.labels.federal_tax_amt"),
total: job.job_totals.totals.federal_tax,
},
{
key: `${
bodyshop.md_responsibility_centers.taxes.tax_ty2?.tax_type2 ||
"T2"
} - ${[
job.cieca_pft.ty2_rate1,
job.cieca_pft.ty2_rate2,
job.cieca_pft.ty2_rate3,
job.cieca_pft.ty2_rate4,
job.cieca_pft.ty2_rate5,
]
.filter((i) => i > 0)
.join(", ")}%`,
total: job.job_totals.totals.us_sales_tax_breakdown.ty2Tax,
},
{
key: `${
bodyshop.md_responsibility_centers.taxes.tax_ty3?.tax_type3 ||
"T3"
} - ${[
job.cieca_pft.ty3_rate1,
job.cieca_pft.ty3_rate2,
job.cieca_pft.ty3_rate3,
job.cieca_pft.ty3_rate4,
job.cieca_pft.ty3_rate5,
]
.filter((i) => i > 0)
.join(", ")}%`,
total: job.job_totals.totals.us_sales_tax_breakdown.ty3Tax,
},
{
key: `${
bodyshop.md_responsibility_centers.taxes.tax_ty4?.tax_type4 ||
"T4"
} - ${[
job.cieca_pft.ty4_rate1,
job.cieca_pft.ty4_rate2,
job.cieca_pft.ty4_rate3,
job.cieca_pft.ty4_rate4,
job.cieca_pft.ty4_rate5,
]
.filter((i) => i > 0)
.join(", ")}%`,
total: job.job_totals.totals.us_sales_tax_breakdown.ty4Tax,
},
{
key: `${
bodyshop.md_responsibility_centers.taxes.tax_ty5?.tax_type5 ||
"TT"
} - ${[
job.cieca_pft.ty5_rate1,
job.cieca_pft.ty5_rate2,
job.cieca_pft.ty5_rate3,
job.cieca_pft.ty5_rate4,
job.cieca_pft.ty5_rate5,
]
.filter((i) => i > 0)
.join(", ")}%`,
total: job.job_totals.totals.us_sales_tax_breakdown.ty5Tax,
},
{
key: t("jobs.labels.total_sales_tax"),
bold: true,
total: Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty1Tax)
.add(
Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty2Tax)
)
.add(
Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty3Tax)
)
.add(
Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty4Tax)
)
.add(
Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty5Tax)
).toJSON(),
},
].filter((item) => item.total.amount !== 0)
: [
{
key: t("jobs.labels.state_tax_amt"),
total: job.job_totals.totals.state_tax,
},
]),
{
key: t("jobs.labels.total_repairs"),
total: job.job_totals.totals.total_repairs,
@@ -58,10 +141,10 @@ 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,
},
// {
// key: t("jobs.fields.federal_tax_payable"),
// total: job.job_totals.totals.custPayable.federal_tax,
// },
{
key: t("jobs.fields.other_amount_payable"),
total: job.job_totals.totals.custPayable.other_customer_amount,
@@ -82,7 +165,7 @@ export function JobTotalsTableTotals({bodyshop, job}) {
bold: true,
},
];
}, [job.job_totals, t, bodyshop.region_config]);
}, [job.job_totals, job.cieca_pft, t, bodyshop.md_responsibility_centers]);
const columns = [
{

View File

@@ -2,8 +2,7 @@ import {useMutation} from "@apollo/client";
import {Button, Space, notification} from "antd";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import dayjs from "../../utils/day";
import dayjs from '../../utils/day';
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {INSERT_EXPORT_LOG} from "../../graphql/accounting.queries";

View File

@@ -1,5 +1,5 @@
import {useMutation} from "@apollo/client";
import {notification, Switch} from "antd";
import { notification, Switch} from "antd";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";

View File

@@ -25,7 +25,7 @@ export const GetSupplementDelta = async (client, jobId, newLines) => {
//Found a relevant matching line. Add it to lines to update.
linesToUpdate.push({
id: existingLines[matchingIndex].id,
newData: {...newLine, removed: false},
newData: {...newLine, removed: false, act_price_before_ppc: null},
});
//Splice out item we found for performance.

View File

@@ -1,8 +1,8 @@
import {gql, useApolloClient, useLazyQuery, useMutation, useQuery,} from "@apollo/client";
import {useSplitTreatments} from "@splitsoftware/splitio-react";
import {Col, notification, Row} from "antd";
import {Button, Col, notification, Row} from "antd";
import Axios from "axios";
import Dinero from "dinero.js";
import _ from "lodash";
import dayjs from "../../utils/day";
import queryString from "query-string";
import React, {useCallback, useEffect, useState} from "react";
@@ -32,6 +32,7 @@ import OwnerFindModalContainer from "../owner-find-modal/owner-find-modal.contai
import {GetSupplementDelta} from "./jobs-available-supplement.estlines.util";
import HeaderFields from "./jobs-available-supplement.headerfields";
import JobsAvailableTableComponent from "./jobs-available-table.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -82,14 +83,15 @@ export function JobsAvailableContainer({bodyshop, currentUser, insertAuditTrail,
const modalSearchState = useState("");
//Import Scenario
const onOwnerFindModalOk = async () => {
const onOwnerFindModalOk = async (lazyData) => {
logImEXEvent("job_import_new");
setOwnerModalVisible(false);
setInsertLoading(true);
const estData = replaceEmpty(estDataRaw.data.available_jobs_by_pk);
const estData = replaceEmpty(
lazyData?.available_jobs_by_pk || estDataRaw.data.available_jobs_by_pk
);
if (!(estData && estData.est_data)) {
//We don't have the right data. Error!
@@ -99,17 +101,24 @@ export function JobsAvailableContainer({bodyshop, currentUser, insertAuditTrail,
});
return;
}
//IO-539 Check for Parts Rate on PAL for SGI use case.
await CheckTaxRates(estData.est_data, bodyshop);
// if (process.env.REACT_APP_COUNTRY === "USA") {
//Massage the CCC file set to remove duplicate UNQ_SEQ.
InstanceRenderManager({executeFunction:true,rome: ResolveCCCLineIssues(estData.est_data, bodyshop) })
const newTotals = (
await Axios.post("/job/totals", {
job: {
...estData.est_data,
joblines: estData.est_data.joblines.data,
},
})
).data;
// } else {
//IO-539 Check for Parts Rate on PAL for SGI use case.
//TODO:AIO Check that the async function is actually waiting before moving on.
InstanceRenderManager({executeFunction: true, imex: await CheckTaxRates(estData.est_data, bodyshop), rome: await CheckTaxRatesUSA(estData.est_data, bodyshop)})
// }
// const newTotals = (
// await Axios.post("/job/totals", {
// job: {
// ...estData.est_data,
// joblines: estData.est_data.joblines.data,
// },
// })
// ).data;
let existingVehicles;
if (estData.est_data.v_vin) {
@@ -124,9 +133,9 @@ export function JobsAvailableContainer({bodyshop, currentUser, insertAuditTrail,
const newJob = {
...estData.est_data,
clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"),
job_totals: newTotals,
// clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
// owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"),
// job_totals: newTotals,
date_open: dayjs(),
status: bodyshop.md_ro_statuses.default_imported,
notes: {
@@ -150,17 +159,23 @@ export function JobsAvailableContainer({bodyshop, currentUser, insertAuditTrail,
delete newJob.vehicle;
}
if (typeof newJob.kmin === "string") {
newJob.kmin = null;
}
try {
const r = await insertNewJob({
variables: {
job: newJob,
},
});
await Axios.post("/job/totalsssu", {
id: r.data.insert_jobs.returning[0].id,
});
if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(r.data.insert_jobs.returning[0].id);
}
notification["success"]({
message: t("jobs.successes.created"),
onClick: () => {
@@ -174,7 +189,7 @@ export function JobsAvailableContainer({bodyshop, currentUser, insertAuditTrail,
operation: AuditTrailMapping.jobimported(),
});
deleteJob({
await deleteJob({
variables: {id: estData.id},
}).then((r) => {
refetch();
@@ -182,18 +197,15 @@ export function JobsAvailableContainer({bodyshop, currentUser, insertAuditTrail,
});
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
} catch (err) {
} catch (r) {
//error while inserting
notification["error"]({
message: t("jobs.errors.creating", {error: err.message}),
});
refetch().catch(e => {
console.error(`Something went wrong in jobs available table container - ${err.message || ''}`)
message: t("jobs.errors.creating", {error: r.message}),
});
refetch();
setInsertLoading(false);
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
}
};
//Supplement scenario
@@ -215,7 +227,9 @@ export function JobsAvailableContainer({bodyshop, currentUser, insertAuditTrail,
//create upsert job
let supp = replaceEmpty({...estData.est_data});
//IO-539 Check for Parts Rate on PAL for SGI use case.
await CheckTaxRates(supp, bodyshop);
InstanceRenderManager({executeFunction:true, imex: await CheckTaxRates(supp, bodyshop), rome: await CheckTaxRatesUSA(supp, bodyshop)})
InstanceRenderManager({executeFunction:true ,rome: ResolveCCCLineIssues(supp, bodyshop) })
delete supp.owner;
delete supp.vehicle;
@@ -391,6 +405,28 @@ export function JobsAvailableContainer({bodyshop, currentUser, insertAuditTrail,
partsQueueToggle={partsQueueToggle}
setPartsQueueToggle={setPartsQueueToggle}
/>
{
// currentUser.email.includes("@rome.") ||
// currentUser.email.includes("@imex.") ? (
// <Button
// onClick={async () => {
// for (const record of data.available_jobs) {
// //Query the data
// console.log("Start Job", record.id);
// const {data} = await loadEstData({
// variables: {id: record.id},
// });
// console.log("Query has been awaited and is complete");
// await onOwnerFindModalOk(data);
// }
// }}
// >
// Add all jobs as new.
// </Button>
// ) : null
}
<Row gutter={[16, 16]}>
<Col span={24}>
<JobsAvailableTableComponent
@@ -422,116 +458,159 @@ function replaceEmpty(someObj, replaceValue = null) {
return JSON.parse(temp);
}
async function CheckTaxRatesUSA(estData,bodyshop){
if (!estData.parts_tax_rates?.PAM) {
estData.parts_tax_rates.PAM = estData.parts_tax_rates.PAC;
}
}
async function CheckTaxRates(estData, bodyshop) {
//LKQ Check
if (
!estData.parts_tax_rates?.PAL ||
estData.parts_tax_rates?.PAL?.prt_tax_rt === null ||
estData.parts_tax_rates?.PAL?.prt_tax_rt === 0
!estData.parts_tax_rates?.PAL ||
estData.parts_tax_rates?.PAL?.prt_tax_rt === null ||
estData.parts_tax_rates?.PAL?.prt_tax_rt === 0
) {
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for LKQ parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for LKQ parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
);
if (res) {
if (!estData.parts_tax_rates.PAL) {
estData.parts_tax_rates.PAL = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAL",
};
}
if (res) {
if (!estData.parts_tax_rates.PAL) {
estData.parts_tax_rates.PAL = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAL",
};
}
estData.parts_tax_rates.PAL.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.parts_tax_rates.PAL.prt_tax_in = true;
}
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.parts_tax_rates.PAL.prt_tax_in = true;
}
}
//PAC Check
if (
!estData.parts_tax_rates?.PAC ||
estData.parts_tax_rates?.PAC?.prt_tax_rt === null ||
estData.parts_tax_rates?.PAC?.prt_tax_rt === 0
!estData.parts_tax_rates?.PAC ||
estData.parts_tax_rates?.PAC?.prt_tax_rt === null ||
estData.parts_tax_rates?.PAC?.prt_tax_rt === 0
) {
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for rechromed parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for rechromed parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
);
if (res) {
if (!estData.parts_tax_rates.PAC) {
estData.parts_tax_rates.PAC = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAC",
};
}
estData.parts_tax_rates.PAC.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.parts_tax_rates.PAC.prt_tax_in = true;
}
if (res) {
if (!estData.parts_tax_rates.PAC) {
estData.parts_tax_rates.PAC = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAC",
};
}
//PAM Check
if (
!estData.parts_tax_rates?.PAM ||
estData.parts_tax_rates?.PAM?.prt_tax_rt === null ||
estData.parts_tax_rates?.PAM?.prt_tax_rt === 0
estData.parts_tax_rates.PAC.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.parts_tax_rates.PAC.prt_tax_in = true;
}
}
//PAM Check
if (
!estData.parts_tax_rates?.PAM ||
estData.parts_tax_rates?.PAM?.prt_tax_rt === null ||
estData.parts_tax_rates?.PAM?.prt_tax_rt === 0
) {
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for remanufactured parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
);
if (res) {
if (!estData.parts_tax_rates.PAM) {
estData.parts_tax_rates.PAM = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAM",
};
}
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for remanufactured parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
);
if (res) {
if (!estData.parts_tax_rates.PAM) {
estData.parts_tax_rates.PAM = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAM",
};
}
estData.parts_tax_rates.PAM.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.parts_tax_rates.PAM.prt_tax_in = true;
}
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.parts_tax_rates.PAM.prt_tax_in = true;
}
}
if (
!estData.parts_tax_rates?.PAR ||
estData.parts_tax_rates?.PAR?.prt_tax_rt === null ||
estData.parts_tax_rates?.PAR?.prt_tax_rt === 0
!estData.parts_tax_rates?.PAR ||
estData.parts_tax_rates?.PAR?.prt_tax_rt === null ||
estData.parts_tax_rates?.PAR?.prt_tax_rt === 0
) {
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for recored parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for recored parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
);
if (res) {
if (!estData.parts_tax_rates.PAR) {
estData.parts_tax_rates.PAR = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAR",
};
}
if (res) {
if (!estData.parts_tax_rates.PAR) {
estData.parts_tax_rates.PAR = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAR",
};
}
estData.parts_tax_rates.PAR.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.parts_tax_rates.PAR.prt_tax_in = true;
}
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.parts_tax_rates.PAR.prt_tax_in = true;
}
}
//IO-1387 If a sublet line is NOT R&R, use the labor tax. If it is, use the sublet tax rate.
//Currently limited to SK shops only.
//if (bodyshop.region_config === "CA_SK") {
estData.joblines.data.forEach((jl, index) => {
if (
(jl.part_type === "PASL" || jl.part_type === "PAS") &&
jl.lbr_op !== "OP11"
) {
estData.joblines.data[index].tax_part = jl.lbr_tax;
}
estData.joblines.data.forEach((jl, index) => {
if (
(jl.part_type === "PASL" || jl.part_type === "PAS") &&
jl.lbr_op !== "OP11"
) {
estData.joblines.data[index].tax_part = jl.lbr_tax;
}
//Set markup lines and tax lines as taxable.
//900510 is a mark up. 900510 is a discount.
if (jl.db_ref === "900510") {
estData.joblines.data[index].tax_part = true;
}
});
//Set markup lines and tax lines as taxable.
//900510 is a mark up. 900510 is a discount.
if (jl.db_ref === "900510") {
estData.joblines.data[index].tax_part = true;
}
});
//}
}
function ResolveCCCLineIssues(estData, bodyshop) {
//Find all misc amounts, populate them to the act price.
//TODO Ensure that this doesnt get violated
//This needs to be done before cleansing unq_seq since some misc prices could move over.
estData.joblines.data.forEach((line) => {
if (line.misc_amt && line.misc_amt !== 0) {
line.act_price = line.act_price + line.misc_amt;
line.tax_part = !!line.misc_tax;
}
});
//Generate the list of duplicated UNQ_SEQ that will feed into the next section to scrub the lines.
const unqSeqHash = _.groupBy(estData.joblines.data, "unq_seq");
const duplicatedUnqSeq = Object.keys(unqSeqHash).filter(
(key) => unqSeqHash[key].length > 1
);
duplicatedUnqSeq.forEach((unq_seq) => {
//Keys are strings, convert to int.
const int_unq_seq = parseInt(unq_seq);
//When line splitting, the first line is always the non-refinish line. We will keep it as is.
//We will cleanse the second line, which is always the next line.
const nonRefLineIndex = estData.joblines.data.findIndex(
(line) => line.unq_seq === int_unq_seq
);
estData.joblines.data[nonRefLineIndex + 1] = {
...estData.joblines.data[nonRefLineIndex + 1],
part_type: null,
act_price: 0,
db_price: 0,
prt_dsmk_p: 0,
prt_dsmk_m: 0,
};
});
}

View File

@@ -37,6 +37,8 @@ export function JobsCloseAutoAllocate({bodyshop, joblines, form, disabled}) {
ret.profitcenter_part = defaults.profits["MAPA"];
} else if (lineDesc.includes("ats amount")) {
ret.profitcenter_part = defaults.profits["ATS"];
} else if (jl.act_price > 0) {
ret.profitcenter_part = defaults.profits["PAO"];
} else {
ret.profitcenter_part = null;
}

View File

@@ -9,10 +9,7 @@ import {createStructuredSelector} from "reselect";
import {auth, logImEXEvent} from "../../firebase/firebase.utils";
import {INSERT_EXPORT_LOG} from "../../graphql/accounting.queries";
import {UPDATE_JOB} from "../../graphql/jobs.queries";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
import client from "../../utils/GraphQLClient";
const mapStateToProps = createStructuredSelector({

View File

@@ -10,8 +10,15 @@ import FormItemEmail from "../form-items-formatted/email-form-item.component";
import FormItemPhone, {PhoneItemFormatterValidation,} from "../form-items-formatted/phone-form-item.component";
import JobsDetailRatesChangeButton from "../jobs-detail-rates-change-button/jobs-detail-rates-change-button.component";
import JobsDetailRatesParts from "../jobs-detail-rates/jobs-detail-rates.parts.component";
import JobsDetailRatesLabor from "../jobs-detail-rates/jobs-detail-rates.labor.component";
import JobsDetailRatesMaterials from "../jobs-detail-rates/jobs-detail-rates.materials.component";
import JobsDetailRatesOther from "../jobs-detail-rates/jobs-detail-rates.other.component";
import JobsDetailRatesTaxes from "../jobs-detail-rates/jobs-detail-rates.taxes.component";
import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -257,26 +264,31 @@ export function JobsCreateJobsInfo({bodyshop, form, selected}) {
<CurrencyInput/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow>
<Form.Item
label={t("jobs.fields.federal_tax_rate")}
name="federal_tax_rate"
>
<InputNumber min={0} max={1} precision={2}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.state_tax_rate")}
name="state_tax_rate"
>
<InputNumber min={0} max={1} precision={2}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.local_tax_rate")}
name="local_tax_rate"
>
<InputNumber min={0} max={1} precision={2}/>
</Form.Item>
</LayoutFormRow>
{
InstanceRenderManager({imex:
<LayoutFormRow>
<Form.Item
label={t("jobs.fields.federal_tax_rate")}
name="federal_tax_rate"
>
<InputNumber min={0} max={1} precision={2} />
</Form.Item>
<Form.Item
label={t("jobs.fields.state_tax_rate")}
name="state_tax_rate"
>
<InputNumber min={0} max={1} precision={2} />
</Form.Item>
<Form.Item
label={t("jobs.fields.local_tax_rate")}
name="local_tax_rate"
>
<InputNumber min={0} max={1} precision={2} />
</Form.Item>
</LayoutFormRow>
})
}
<LayoutFormRow>
<Form.Item label={t("jobs.fields.rate_lab")} name="rate_lab">
<CurrencyInput/>
@@ -354,7 +366,15 @@ export function JobsCreateJobsInfo({bodyshop, form, selected}) {
expanded
required={selected && true}
form={form}
/>
/>{
InstanceRenderManager({rome:
<>
<JobsDetailRatesLabor form={form}/>
<JobsDetailRatesMaterials form={form}/>
<JobsDetailRatesOther form={form}/>
<JobsDetailRatesTaxes form={form}/>
</>})
}
</div>
);
}

View File

@@ -43,5 +43,4 @@ export default function AddToProduction(
//insert the new job. call the callback with the returned ID when done.
return;
}

View File

@@ -50,6 +50,8 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(setModalContext({context: context, modal: "cardPayment"})),
insertAuditTrail: ({jobid, operation}) =>
dispatch(insertAuditTrail({jobid, operation})),
setTimeTicketTaskContext: (context) =>
dispatch(setModalContext({context: context, modal: "timeTicketTask"})),
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
setMessage: (text) => dispatch(setMessage(text)),
@@ -70,7 +72,8 @@ export function JobsDetailHeaderActions({
insertAuditTrail,
setEmailOptions,
openChatByPhone,
setMessage
setMessage,
setTimeTicketTaskContext,
}) {
const {t} = useTranslation();
const client = useApolloClient();
@@ -678,7 +681,23 @@ export function JobsDetailHeaderActions({
},
});
}
},
}];
if (bodyshop.md_tasks_presets.enable_tasks) {
menuItems.push({
key: 'claimtimetickettasks',
disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced),
onClick: () => {
setTimeTicketTaskContext({
actions: {},
context: {jobid: job.id},
});
},
label: t("timetickets.actions.claimtasks")
});
}
menuItems.push(
{
key: 'enterpayments',
disabled: !job.converted,
@@ -691,7 +710,7 @@ export function JobsDetailHeaderActions({
context: {jobid: job.id},
});
}
}];
});
if (ImEXPay.treatment === "on") {
menuItems.push({

View File

@@ -59,7 +59,7 @@ export default async function DuplicateJob(
//insert the new job. call the callback with the returned ID when done.
return;
return;
}
export async function CreateIouForJob(

View File

@@ -5,9 +5,13 @@ import {createStructuredSelector} from "reselect";
import {selectJobReadOnly} from "../../redux/application/application.selectors";
import LaborAllocationsTableComponent from "../labor-allocations-table/labor-allocations-table.component";
import TimeTicketList from "../time-ticket-list/time-ticket-list.component";
import PayrollLaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.payroll.component";
import {selectBodyshop} from "../../redux/user/user.selectors";
import {useSplitTreatments} from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
bodyshop: selectBodyshop,
});
export default connect(mapStateToProps, null)(JobsDetailLaborContainer);
@@ -48,6 +52,7 @@ const adjSpan = {
};
export function JobsDetailLaborContainer({
bodyshop,
jobRO,
job,
jobId,
@@ -58,6 +63,13 @@ export function JobsDetailLaborContainer({
techConsole,
adjustments,
}) {
const {treatments: {Enhanced_Payroll}} = useSplitTreatments({
attributes: {},
names: ["Enhanced_Payroll"],
splitKey: bodyshop.imexshopid,
});
return (
<Row gutter={[16, 16]}>
<Col {...ticketSpan}>
@@ -70,14 +82,28 @@ export function JobsDetailLaborContainer({
jobId={jobId}
/>
</Col>
<Col {...adjSpan}>
<LaborAllocationsTableComponent
jobId={jobId}
joblines={joblines}
timetickets={timetickets}
adjustments={adjustments}
/>
</Col>
{Enhanced_Payroll.treatment === "on" ? (
<Col {...adjSpan}>
<PayrollLaborAllocationsTable
jobId={jobId}
joblines={joblines}
timetickets={timetickets}
refetch={refetch}
adjustments={adjustments}
/>
</Col>
) : (
<Col {...adjSpan}>
<LaborAllocationsTableComponent
jobId={jobId}
joblines={joblines}
timetickets={timetickets}
refetch={refetch}
adjustments={adjustments}
/>
</Col>
)}
</Row>
);
}

View File

@@ -6,12 +6,14 @@ import BillsListTable from "../bills-list-table/bills-list-table.component";
import JobBillsTotal from "../job-bills-total/job-bills-total.component";
import PartsOrderListTableComponent from "../parts-order-list-table/parts-order-list-table.component";
import PartsOrderModal from "../parts-order-modal/parts-order-modal.container";
import PartsDispatchTable from "../parts-dispatch-table/parts-dispatch-table.component";
export default function JobsDetailPliComponent({
job,
billsQuery,
handleBillOnRowClick,
handlePartsOrderOnRowClick,
handlePartsDispatchOnRowClick,
}) {
return (
<div>
@@ -43,6 +45,13 @@ export default function JobsDetailPliComponent({
billsQuery={billsQuery}
/>
</Col>
<Col span={24}>
<PartsDispatchTable
job={job}
handleOnRowClick={handlePartsDispatchOnRowClick}
billsQuery={billsQuery}
/>
</Col>
</Row>
</div>
);

View File

@@ -39,12 +39,24 @@ export default function JobsDetailPliContainer({job}) {
}
};
const handlePartsDispatchOnRowClick = (record) => {
if (record) {
if (record.id) {
search.partsdispatchid = record.id;
history.push({search: queryString.stringify(search)});
}
} else {
delete search.partsdispatchid;
history.push({search: queryString.stringify(search)});
}
};
return (
<JobsDetailPliComponent
job={job}
billsQuery={billsQuery}
handleBillOnRowClick={handleBillOnRowClick}
handlePartsOrderOnRowClick={handlePartsOrderOnRowClick}
handlePartsDispatchOnRowClick={handlePartsDispatchOnRowClick}
/>
);
}

View File

@@ -10,7 +10,13 @@ import CurrencyInput from "../form-items-formatted/currency-form-item.component"
import JobsDetailRatesChangeButton from "../jobs-detail-rates-change-button/jobs-detail-rates-change-button.component";
import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
import FormRow from "../layout-form-row/layout-form-row.component";
import JobsDetailRatesLabor from "./jobs-detail-rates.labor.component";
import JobsDetailRatesMaterials from "./jobs-detail-rates.materials.component";
import JobsDetailRatesOther from "./jobs-detail-rates.other.component";
import JobsDetailRatesParts from "./jobs-detail-rates.parts.component";
import JobsDetailRatesTaxes from "./jobs-detail-rates.taxes.component";
import JobsDetailRatesProfileOVerride from "./jobs-detail-rates.profile-override.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
@@ -111,41 +117,44 @@ export function JobsDetailRates({jobRO, form, job, bodyshop}) {
}}
</Form.Item>
</FormRow>
<FormRow>
<Form.Item
label={t("jobs.fields.federal_tax_rate")}
name="federal_tax_rate"
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.state_tax_rate")}
name="state_tax_rate"
>
<InputNumber
min={0}
max={1}
precision={2}
disabled={jobRO}
autoComplete="new-password"
/>
</Form.Item>
<Form.Item
label={t("jobs.fields.local_tax_rate")}
name="local_tax_rate"
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
</Form.Item>
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
<Form.Item
label={t("jobs.fields.ca_gst_registrant")}
name="ca_gst_registrant"
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
)}
</FormRow>
{
InstanceRenderManager({imex:
<FormRow>
<Form.Item
label={t("jobs.fields.federal_tax_rate")}
name="federal_tax_rate"
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.state_tax_rate")}
name="state_tax_rate"
>
<InputNumber
min={0}
max={1}
precision={2}
disabled={jobRO}
autoComplete="new-password"
/>
</Form.Item>
<Form.Item
label={t("jobs.fields.local_tax_rate")}
name="local_tax_rate"
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
</Form.Item>
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
<Form.Item
label={t("jobs.fields.ca_gst_registrant")}
name="ca_gst_registrant"
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
)}
</FormRow>})
}
<Divider
orientation="left"
type="horizontal"
@@ -233,8 +242,19 @@ export function JobsDetailRates({jobRO, form, job, bodyshop}) {
<CurrencyInput min={0} disabled={jobRO}/>
</Form.Item>
</FormRow>
{
InstanceRenderManager({rome: <>
<Divider orientation="left">Tax Profile</Divider>
<JobsDetailRatesProfileOVerride form={form}/>
<JobsDetailRatesParts form={form}/>
</div>
<JobsDetailRatesLabor form={form}/>
<JobsDetailRatesMaterials form={form}/>
<JobsDetailRatesOther form={form}/>
<JobsDetailRatesTaxes form={form}/>
</>})
}
</div>
);
}

View File

@@ -0,0 +1,429 @@
import {Collapse, Form, Switch} from "antd";
import React from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectJobReadOnly} from "../../redux/application/application.selectors";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
});
export function JobsDetailRatesLabor({
jobRO,
expanded,
required = true,
form,
}) {
const {t} = useTranslation();
return (
<Collapse defaultActiveKey={expanded && "rates"}>
<Collapse.Panel
forceRender
header={t("jobs.labels.cieca_pfl")}
key="cieca_pfl"
>
<LayoutFormRow header={t("joblines.fields.lbr_types.LAB")}>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
name={["cieca_pfl", "LAB", "lbr_tax_in"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
name={["cieca_pfl", "LAB", "lbr_tx_in1"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
name={["cieca_pfl", "LAB", "lbr_tx_in2"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
name={["cieca_pfl", "LAB", "lbr_tx_in3"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
name={["cieca_pfl", "LAB", "lbr_tx_in4"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
name={["cieca_pfl", "LAB", "lbr_tx_in5"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.lbr_types.LAD")}>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
name={["cieca_pfl", "LAD", "lbr_tax_in"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
name={["cieca_pfl", "LAD", "lbr_tx_in1"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
name={["cieca_pfl", "LAD", "lbr_tx_in2"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
name={["cieca_pfl", "LAD", "lbr_tx_in3"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
name={["cieca_pfl", "LAD", "lbr_tx_in4"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
name={["cieca_pfl", "LAD", "lbr_tx_in5"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.lbr_types.LAE")}>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
name={["cieca_pfl", "LAE", "lbr_tax_in"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
name={["cieca_pfl", "LAE", "lbr_tx_in1"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
name={["cieca_pfl", "LAE", "lbr_tx_in2"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
name={["cieca_pfl", "LAE", "lbr_tx_in3"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
name={["cieca_pfl", "LAE", "lbr_tx_in4"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
name={["cieca_pfl", "LAE", "lbr_tx_in5"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.lbr_types.LAF")}>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
name={["cieca_pfl", "LAF", "lbr_tax_in"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
name={["cieca_pfl", "LAF", "lbr_tx_in1"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
name={["cieca_pfl", "LAF", "lbr_tx_in2"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
name={["cieca_pfl", "LAF", "lbr_tx_in3"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
name={["cieca_pfl", "LAF", "lbr_tx_in4"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
name={["cieca_pfl", "LAF", "lbr_tx_in5"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.lbr_types.LAG")}>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
name={["cieca_pfl", "LAG", "lbr_tax_in"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
name={["cieca_pfl", "LAG", "lbr_tx_in1"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
name={["cieca_pfl", "LAG", "lbr_tx_in2"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
name={["cieca_pfl", "LAG", "lbr_tx_in3"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
name={["cieca_pfl", "LAG", "lbr_tx_in4"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
name={["cieca_pfl", "LAG", "lbr_tx_in5"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.lbr_types.LAM")}>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
name={["cieca_pfl", "LAM", "lbr_tax_in"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
name={["cieca_pfl", "LAM", "lbr_tx_in1"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
name={["cieca_pfl", "LAM", "lbr_tx_in2"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
name={["cieca_pfl", "LAM", "lbr_tx_in3"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
name={["cieca_pfl", "LAM", "lbr_tx_in4"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
name={["cieca_pfl", "LAM", "lbr_tx_in5"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.lbr_types.LAR")}>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
name={["cieca_pfl", "LAR", "lbr_tax_in"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
name={["cieca_pfl", "LAR", "lbr_tx_in1"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
name={["cieca_pfl", "LAR", "lbr_tx_in2"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
name={["cieca_pfl", "LAR", "lbr_tx_in3"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
name={["cieca_pfl", "LAR", "lbr_tx_in4"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
name={["cieca_pfl", "LAR", "lbr_tx_in5"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.lbr_types.LAS")}>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
name={["cieca_pfl", "LAS", "lbr_tax_in"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
name={["cieca_pfl", "LAS", "lbr_tx_in1"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
name={["cieca_pfl", "LAS", "lbr_tx_in2"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
name={["cieca_pfl", "LAS", "lbr_tx_in3"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
name={["cieca_pfl", "LAS", "lbr_tx_in4"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
name={["cieca_pfl", "LAS", "lbr_tx_in5"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.lbr_types.LAU")}>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
name={["cieca_pfl", "LAU", "lbr_tax_in"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
name={["cieca_pfl", "LAU", "lbr_tx_in1"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
name={["cieca_pfl", "LAU", "lbr_tx_in2"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
name={["cieca_pfl", "LAU", "lbr_tx_in3"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
name={["cieca_pfl", "LAU", "lbr_tx_in4"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
name={["cieca_pfl", "LAU", "lbr_tx_in5"]}
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
</LayoutFormRow>
</Collapse.Panel>
</Collapse>
);
}
export default connect(mapStateToProps, null)(JobsDetailRatesLabor);

View File

@@ -0,0 +1,147 @@
import {Collapse, Form, Input, InputNumber, Switch} from "antd";
import React from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectJobReadOnly} from "../../redux/application/application.selectors";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
});
export function JobsDetailRatesMaterials({
jobRO,
expanded,
required = true,
form,
}) {
const {t} = useTranslation();
return (
<Collapse defaultActiveKey={expanded && "rates"}>
<Collapse.Panel
forceRender
header={t("jobs.fields.materials.materials")}
key="materials"
>
<LayoutFormRow header={t("jobs.fields.materials.MAPA")}>
<Form.Item
label={t("jobs.fields.materials.cal_maxdlr")}
name={["materials", "MAPA", "cal_maxdlr"]}
>
<InputNumber min={0} precision={2} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.cal_opcode")}
name={["materials", "MAPA", "cal_opcode"]}
>
<Input disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.tax_ind")}
name={["materials", "MAPA", "tax_ind"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.mat_tx_in1")}
name={["materials", "MAPA", "mat_tx_in1"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.mat_tx_in2")}
name={["materials", "MAPA", "mat_tx_in2"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.mat_tx_in3")}
name={["materials", "MAPA", "mat_tx_in3"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.mat_tx_in4")}
name={["materials", "MAPA", "mat_tx_in4"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.mat_tx_in5")}
name={["materials", "MAPA", "mat_tx_in5"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("jobs.fields.materials.MASH")}>
<Form.Item
label={t("jobs.fields.materials.cal_maxdlr")}
name={["materials", "MASH", "cal_maxdlr"]}
>
<InputNumber min={0} precision={2} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.cal_opcode")}
name={["materials", "MASH", "cal_opcode"]}
>
<Input disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.tax_ind")}
name={["materials", "MASH", "tax_ind"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.mat_tx_in1")}
name={["materials", "MASH", "mat_tx_in1"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.mat_tx_in2")}
name={["materials", "MASH", "mat_tx_in2"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.mat_tx_in3")}
name={["materials", "MASH", "mat_tx_in3"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.mat_tx_in4")}
name={["materials", "MASH", "mat_tx_in4"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.mat_tx_in5")}
name={["materials", "MASH", "mat_tx_in5"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
</LayoutFormRow>
</Collapse.Panel>
</Collapse>
);
}
export default connect(mapStateToProps, null)(JobsDetailRatesMaterials);

View File

@@ -0,0 +1,106 @@
import {Collapse, Form, Switch} from "antd";
import React from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectJobReadOnly} from "../../redux/application/application.selectors";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
});
export function JobsDetailRatesOther({
jobRO,
expanded,
required = true,
form,
}) {
const {t} = useTranslation();
return (
<Collapse defaultActiveKey={expanded && "rates"}>
<Collapse.Panel
forceRender
header={t("jobs.labels.cieca_pfo")}
key="cieca_pfo"
>
<LayoutFormRow noDivider>
<Form.Item
label={t("jobs.fields.cieca_pfo.tow_t_in1")}
name={["cieca_pfo", "tow_t_in1"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfo.tow_t_in2")}
name={["cieca_pfo", "tow_t_in2"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfo.tow_t_in3")}
name={["cieca_pfo", "tow_t_in3"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfo.tow_t_in4")}
name={["cieca_pfo", "tow_t_in4"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfo.tow_t_in5")}
name={["cieca_pfo", "tow_t_in5"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfo.stor_t_in1")}
name={["cieca_pfo", "stor_t_in1"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfo.stor_t_in2")}
name={["cieca_pfo", "stor_t_in2"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfo.stor_t_in3")}
name={["cieca_pfo", "stor_t_in3"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfo.stor_t_in4")}
name={["cieca_pfo", "stor_t_in4"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfo.stor_t_in5")}
name={["cieca_pfo", "stor_t_in5"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
</LayoutFormRow>
</Collapse.Panel>
</Collapse>
);
}
export default connect(mapStateToProps, null)(JobsDetailRatesOther);

View File

@@ -30,7 +30,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "PAA", "prt_discp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -43,7 +43,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "PAA", "prt_mkupp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -69,18 +69,58 @@ export function JobsDetailRatesParts({
},
]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
name={["parts_tax_rates", "PAA", "prt_tx_in1"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
name={["parts_tax_rates", "PAA", "prt_tx_in2"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
name={["parts_tax_rates", "PAA", "prt_tx_in3"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
name={["parts_tax_rates", "PAA", "prt_tx_in4"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
name={["parts_tax_rates", "PAA", "prt_tx_in5"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.PAC")}>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "PAC", "prt_discp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -93,7 +133,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "PAC", "prt_mkupp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -119,18 +159,58 @@ export function JobsDetailRatesParts({
},
]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
name={["parts_tax_rates", "PAC", "prt_tx_in1"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
name={["parts_tax_rates", "PAC", "prt_tx_in2"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
name={["parts_tax_rates", "PAC", "prt_tx_in3"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
name={["parts_tax_rates", "PAC", "prt_tx_in4"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
name={["parts_tax_rates", "PAC", "prt_tx_in5"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.PAL")}>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "PAL", "prt_discp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -143,7 +223,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "PAL", "prt_mkupp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -169,18 +249,58 @@ export function JobsDetailRatesParts({
},
]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
name={["parts_tax_rates", "PAL", "prt_tx_in1"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
name={["parts_tax_rates", "PAL", "prt_tx_in2"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
name={["parts_tax_rates", "PAL", "prt_tx_in3"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
name={["parts_tax_rates", "PAL", "prt_tx_in4"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
name={["parts_tax_rates", "PAL", "prt_tx_in5"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.PAG")}>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "PAG", "prt_discp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -193,7 +313,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "PAG", "prt_mkupp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -219,18 +339,58 @@ export function JobsDetailRatesParts({
},
]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
name={["parts_tax_rates", "PAG", "prt_tx_in1"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
name={["parts_tax_rates", "PAG", "prt_tx_in2"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
name={["parts_tax_rates", "PAG", "prt_tx_in3"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
name={["parts_tax_rates", "PAG", "prt_tx_in4"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
name={["parts_tax_rates", "PAG", "prt_tx_in5"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.PAM")}>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "PAM", "prt_discp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -243,7 +403,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "PAM", "prt_mkupp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -269,18 +429,58 @@ export function JobsDetailRatesParts({
},
]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
name={["parts_tax_rates", "PAM", "prt_tx_in1"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
name={["parts_tax_rates", "PAM", "prt_tx_in2"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
name={["parts_tax_rates", "PAM", "prt_tx_in3"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
name={["parts_tax_rates", "PAM", "prt_tx_in4"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
name={["parts_tax_rates", "PAM", "prt_tx_in5"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.PAN")}>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "PAN", "prt_discp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -293,7 +493,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "PAN", "prt_mkupp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -319,18 +519,58 @@ export function JobsDetailRatesParts({
},
]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
name={["parts_tax_rates", "PAN", "prt_tx_in1"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
name={["parts_tax_rates", "PAN", "prt_tx_in2"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
name={["parts_tax_rates", "PAN", "prt_tx_in3"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
name={["parts_tax_rates", "PAN", "prt_tx_in4"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
name={["parts_tax_rates", "PAN", "prt_tx_in5"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.PAO")}>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "PAO", "prt_discp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -343,7 +583,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "PAO", "prt_mkupp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -369,18 +609,58 @@ export function JobsDetailRatesParts({
},
]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
name={["parts_tax_rates", "PAO", "prt_tx_in1"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
name={["parts_tax_rates", "PAO", "prt_tx_in2"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
name={["parts_tax_rates", "PAO", "prt_tx_in3"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
name={["parts_tax_rates", "PAO", "prt_tx_in4"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
name={["parts_tax_rates", "PAO", "prt_tx_in5"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.PAP")}>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "PAP", "prt_discp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -393,7 +673,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "PAP", "prt_mkupp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -419,18 +699,58 @@ export function JobsDetailRatesParts({
},
]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
name={["parts_tax_rates", "PAP", "prt_tx_in1"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
name={["parts_tax_rates", "PAP", "prt_tx_in2"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
name={["parts_tax_rates", "PAP", "prt_tx_in3"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
name={["parts_tax_rates", "PAP", "prt_tx_in4"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
name={["parts_tax_rates", "PAP", "prt_tx_in5"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.PAR")}>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "PAR", "prt_discp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -443,7 +763,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "PAR", "prt_mkupp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -469,18 +789,58 @@ export function JobsDetailRatesParts({
},
]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
name={["parts_tax_rates", "PAR", "prt_tx_in1"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
name={["parts_tax_rates", "PAR", "prt_tx_in2"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
name={["parts_tax_rates", "PAR", "prt_tx_in3"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
name={["parts_tax_rates", "PAR", "prt_tx_in4"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
name={["parts_tax_rates", "PAR", "prt_tx_in5"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.PAS")}>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "PAS", "prt_discp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -493,7 +853,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "PAS", "prt_mkupp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -519,18 +879,58 @@ export function JobsDetailRatesParts({
},
]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
name={["parts_tax_rates", "PAS", "prt_tx_in1"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
name={["parts_tax_rates", "PAS", "prt_tx_in2"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
name={["parts_tax_rates", "PAS", "prt_tx_in3"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
name={["parts_tax_rates", "PAS", "prt_tx_in4"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
name={["parts_tax_rates", "PAS", "prt_tx_in5"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.PASL")}>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "PASL", "prt_discp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -543,7 +943,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "PASL", "prt_mkupp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -569,18 +969,58 @@ export function JobsDetailRatesParts({
},
]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
name={["parts_tax_rates", "PASL", "prt_tx_in1"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
name={["parts_tax_rates", "PASL", "prt_tx_in2"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
name={["parts_tax_rates", "PASL", "prt_tx_in3"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
name={["parts_tax_rates", "PASL", "prt_tx_in4"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
name={["parts_tax_rates", "PASL", "prt_tx_in5"]}
valuePropName="checked"
>
<Switch/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.CCDR")}>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "CCDR", "prt_discp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -593,7 +1033,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "CCDR", "prt_mkupp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -606,7 +1046,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_tax_rt")}
name={["parts_tax_rates", "CCDR", "prt_tax_rt"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.CCF")}>
@@ -614,7 +1054,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "CCF", "prt_discp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -627,7 +1067,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "CCF", "prt_mkupp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -640,7 +1080,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_tax_rt")}
name={["parts_tax_rates", "CCF", "prt_tax_rt"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.CCM")}>
@@ -648,7 +1088,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "CCM", "prt_discp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -661,7 +1101,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "CCM", "prt_mkupp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -674,7 +1114,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_tax_rt")}
name={["parts_tax_rates", "CCM", "prt_tax_rt"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.CCC")}>
@@ -682,7 +1122,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "CCC", "prt_discp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -695,7 +1135,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "CCC", "prt_mkupp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -708,7 +1148,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_tax_rt")}
name={["parts_tax_rates", "CCC", "prt_tax_rt"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.CCD")}>
@@ -716,7 +1156,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "CCD", "prt_discp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -729,7 +1169,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "CCD", "prt_mkupp"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -742,39 +1182,39 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_tax_rt")}
name={["parts_tax_rates", "CCD", "prt_tax_rt"]}
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow>
<Form.Item label={t("jobs.fields.tax_tow_rt")} name="tax_tow_rt">
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item label={t("jobs.fields.tax_str_rt")} name="tax_str_rt">
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.tax_paint_mat_rt")}
name="tax_paint_mat_rt"
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.tax_shop_mat_rt")}
name="tax_shop_mat_rt"
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item label={t("jobs.fields.tax_sub_rt")} name="tax_sub_rt">
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item label={t("jobs.fields.tax_lbr_rt")} name="tax_lbr_rt">
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.tax_levies_rt")}
name="tax_levies_rt"
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
</Form.Item>
</LayoutFormRow>
</Collapse.Panel>

View File

@@ -0,0 +1,43 @@
import {Button, Popconfirm} from "antd";
import React from "react";
import {useTranslation} from "react-i18next";
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
)(JobsDetailRatesProfileOVerride);
export function JobsDetailRatesProfileOVerride({bodyshop, form}) {
const {t} = useTranslation();
return (
<Popconfirm
onConfirm={() => {
form.setFieldsValue({
cieca_pft: {
...bodyshop.md_responsibility_centers.taxes.tax_ty1,
...bodyshop.md_responsibility_centers.taxes.tax_ty2,
...bodyshop.md_responsibility_centers.taxes.tax_ty3,
...bodyshop.md_responsibility_centers.taxes.tax_ty4,
...bodyshop.md_responsibility_centers.taxes.tax_ty5,
},
materials: bodyshop.md_responsibility_centers.cieca_pfm,
cieca_pfl: bodyshop.md_responsibility_centers.cieca_pfl,
parts_tax_rates: bodyshop.md_responsibility_centers.parts_tax_rates,
});
}}
title={t("jobs.actions.taxprofileoverride_confirm")}
>
<Button type="link">{t("jobs.actions.taxprofileoverride")}</Button>
</Popconfirm>
);
}

View File

@@ -0,0 +1,162 @@
import {Collapse, Divider, Form, Input, InputNumber, Space} from "antd";
import React from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectJobReadOnly} from "../../redux/application/application.selectors";
import {selectBodyshop} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
bodyshop: selectBodyshop,
});
export function JobsDetailRatesTaxes({
jobRO,
expanded,
bodyshop,
required = true,
form,
}) {
const {t} = useTranslation();
const formItems = [];
for (let tyCounter = 1; tyCounter <= 5; tyCounter++) {
const section = [];
section.push(
TaxFormItems({
typeNum: tyCounter,
rootElements: true,
bodyshop,
jobRO,
})
);
for (let iterator = 1; iterator <= 5; iterator++) {
section.push(
TaxFormItems({
typeNum: tyCounter,
typeNumIterator: iterator,
rootElements: false,
jobRO,
})
);
}
formItems.push(<>
<Space wrap>
{section}
</Space>
<Divider/>
</>)
}
return (
<Collapse defaultActiveKey={expanded && "rates"}>
<Collapse.Panel
forceRender
header={t("jobs.labels.cieca_pft")}
key="cieca_pft"
>
{formItems}
</Collapse.Panel>
</Collapse>
);
}
export default connect(mapStateToProps, null)(JobsDetailRatesTaxes);
function TaxFormItems({
typeNum,
typeNumIterator,
rootElements,
bodyshopjobRO,
jobRO,
}) {
const {t} = useTranslation();
if (rootElements)
return (
<>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_tax_type", {
typeNum,
typeNumIterator,
})}
// rules={[
// {
// required: true,
// //message: t("general.validation.required"),
// },
// ]}
name={["cieca_pft", `tax_type${typeNum}`]}
>
<Input disabled={jobRO}/>
</Form.Item>
</>
);
return (
<>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_tax_tier", {
typeNum,
typeNumIterator,
})}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["cieca_pft", `ty${typeNum}_tier${typeNumIterator}`]}
>
<InputNumber precision={0} min={0} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_tax_thres", {
typeNum,
typeNumIterator,
})}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["cieca_pft", `ty${typeNum}_thres${typeNumIterator}`]}
>
<InputNumber min={0} precision={2} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_tax_rate", {
typeNum,
typeNumIterator,
})}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["cieca_pft", `ty${typeNum}_rate${typeNumIterator}`]}
>
<InputNumber min={0} precision={2} disabled={jobRO}/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_tax_sur", {
typeNum,
typeNumIterator,
})}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["cieca_pft", `ty${typeNum}_sur${typeNumIterator}`]}
>
<InputNumber min={0} precision={2} disabled={jobRO}/>
</Form.Item>
</>
);
}

View File

@@ -1,4 +1,4 @@
import {Popconfirm, Button} from "antd";
import {Button, Popconfirm} from "antd";
import React from "react";
import {createStructuredSelector} from "reselect";
import {selectJobReadOnly} from "../../redux/application/application.selectors";

View File

@@ -1,13 +1,5 @@
import {useMutation} from "@apollo/client";
import {
Button,
Card,
Form,
InputNumber,
notification,
Popover,
Select,
} from "antd";
import {Button, Card, Form, InputNumber, notification, Popover, Select,} from "antd";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {UPDATE_JOB} from "../../graphql/jobs.queries";

View File

@@ -0,0 +1,325 @@
import {Button, Card, Col, notification, Row, Space, Table, Typography,} from "antd";
import {SyncOutlined} from "@ant-design/icons";
import axios from "axios";
import _ from "lodash";
import React, {useEffect, useMemo, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectTechnician} from "../../redux/tech/tech.selectors";
import {selectBodyshop} from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import "./labor-allocations-table.styles.scss";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
technician: selectTechnician,
});
export function PayrollLaborAllocationsTable({
jobId,
joblines,
timetickets,
bodyshop,
adjustments,
technician,
refetch,
}) {
const {t} = useTranslation();
const [totals, setTotals] = useState([]);
const [state, setState] = useState({
sortedInfo: {
columnKey: "cost_center",
field: "cost_center",
order: "ascend",
},
filteredInfo: {},
});
useEffect(() => {
async function CalculateTotals() {
const {data} = await axios.post("/payroll/calculatelabor", {
jobid: jobId,
});
setTotals(data);
}
if (!!joblines && !!timetickets && !!bodyshop) {
CalculateTotals();
}
if (!jobId) setTotals([]);
}, [joblines, timetickets, bodyshop, adjustments, jobId]);
const convertedLines = useMemo(
() => joblines && joblines.filter((j) => j.convertedtolbr),
[joblines]
);
const columns = [
{
title: t("timetickets.fields.employee"),
dataIndex: "employeeid",
key: "employeeid",
render: (text, record) => {
if (record.employeeid === undefined) {
return (
<span style={{color: "tomato", fontWeight: "bolder"}}>
{t("timetickets.labels.unassigned")}
</span>
);
}
const emp = bodyshop.employees.find((e) => e.id === record.employeeid);
return `${emp?.first_name} ${emp?.last_name}`;
},
},
{
title: t("joblines.fields.mod_lbr_ty"),
dataIndex: "mod_lbr_ty",
key: "mod_lbr_ty",
render: (text, record) =>
record.employeeid === undefined ? (
<span style={{color: "tomato", fontWeight: "bolder"}}>
{t("timetickets.labels.unassigned")}
</span>
) : (
t(`joblines.fields.lbr_types.${record.mod_lbr_ty?.toUpperCase()}`)
),
},
// {
// title: t("timetickets.fields.rate"),
// dataIndex: "rate",
// key: "rate",
// },
{
title: t("jobs.labels.hrs_total"),
dataIndex: "expectedHours",
key: "expectedHours",
sorter: (a, b) => a.expectedHours - b.expectedHours,
sortOrder:
state.sortedInfo.columnKey === "expectedHours" &&
state.sortedInfo.order,
render: (text, record) => record.expectedHours.toFixed(5),
},
{
title: t("jobs.labels.hrs_claimed"),
dataIndex: "claimedHours",
key: "claimedHours",
sorter: (a, b) => a.claimedHours - b.claimedHours,
sortOrder:
state.sortedInfo.columnKey === "claimedHours" && state.sortedInfo.order,
render: (text, record) =>
record.claimedHours && record.claimedHours.toFixed(5),
},
{
title: t("jobs.labels.difference"),
dataIndex: "difference",
key: "difference",
sorter: (a, b) => a.difference - b.difference,
sortOrder:
state.sortedInfo.columnKey === "difference" && state.sortedInfo.order,
render: (text, record) => {
const difference = _.round(
record.expectedHours - record.claimedHours,
5
);
return (
<strong
style={{
color: difference >= 0 ? "green" : "red",
}}
>
{difference}
</strong>
);
},
},
];
const convertedTableCols = [
{
title: t("joblines.fields.line_desc"),
dataIndex: "line_desc",
key: "line_desc",
ellipsis: true,
},
{
title: t("joblines.fields.op_code_desc"),
dataIndex: "op_code_desc",
key: "op_code_desc",
ellipsis: true,
render: (text, record) =>
`${record.op_code_desc || ""}${
record.alt_partm ? ` ${record.alt_partm}` : ""
}`,
},
{
title: t("joblines.fields.act_price"),
dataIndex: "act_price",
key: "act_price",
ellipsis: true,
render: (text, record) => (
<>
<CurrencyFormatter>
{record.db_ref === "900510" || record.db_ref === "900511"
? record.prt_dsmk_m
: record.act_price}
</CurrencyFormatter>
{record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? (
<span
style={{marginLeft: ".2rem"}}
>{`(${record.prt_dsmk_p}%)`}</span>
) : (
<></>
)}
</>
),
},
{
title: t("joblines.fields.part_qty"),
dataIndex: "part_qty",
key: "part_qty",
},
{
title: t("joblines.fields.mod_lbr_ty"),
dataIndex: "conv_mod_lbr_ty",
key: "conv_mod_lbr_ty",
render: (text, record) =>
record.convertedtolbr_data && record.convertedtolbr_data.mod_lbr_ty,
},
{
title: t("joblines.fields.mod_lb_hrs"),
dataIndex: "conv_mod_lb_hrs",
key: "conv_mod_lb_hrs",
render: (text, record) =>
record.convertedtolbr_data &&
record.convertedtolbr_data.mod_lb_hrs &&
record.convertedtolbr_data.mod_lb_hrs.toFixed(5),
},
];
const handleTableChange = (pagination, filters, sorter) => {
setState({...state, filteredInfo: filters, sortedInfo: sorter});
};
const summary =
totals &&
totals.reduce(
(acc, val) => {
acc.hrs_total += val.expectedHours;
acc.hrs_claimed += val.claimedHours;
// acc.adjustments += val.adjustments;
acc.difference += val.expectedHours - val.claimedHours;
return acc;
},
{hrs_total: 0, hrs_claimed: 0, adjustments: 0, difference: 0}
);
return (
<Row gutter={[16, 16]}>
<Col span={24}>
<Card
title={t("jobs.labels.laborallocations")}
extra={
<Space>
<Button
onClick={async () => {
const response = await axios.post("/payroll/payall", {
jobid: jobId,
});
if (response.status === 200) {
if (response.data.success !== false) {
notification.open({
type: "success",
message: t("timetickets.successes.payall"),
});
} else {
notification.open({
type: "error",
message: t("timetickets.errors.payall", {
error: response.data.error,
}),
});
}
if (refetch) refetch();
} else {
notification.open({
type: "error",
message: t("timetickets.errors.payall", {
error: JSON.stringify(""),
}),
});
}
}}
>
{t("timetickets.actions.payall")}
</Button>
<Button
onClick={async () => {
const {data} = await axios.post("/payroll/calculatelabor", {
jobid: jobId,
});
setTotals(data);
refetch();
}}
>
<SyncOutlined/>
</Button>
</Space>
}
>
<Table
columns={columns}
rowKey={(record) => `${record.employeeid} ${record.mod_lbr_ty}`}
pagination={false}
onChange={handleTableChange}
dataSource={totals}
scroll={{
x: true,
}}
summary={() => (
<Table.Summary.Row>
<Table.Summary.Cell>
<Typography.Title level={4}>
{t("general.labels.totals")}
</Typography.Title>
</Table.Summary.Cell>
<Table.Summary.Cell></Table.Summary.Cell>
<Table.Summary.Cell>
{summary.hrs_total.toFixed(5)}
</Table.Summary.Cell>
<Table.Summary.Cell>
{summary.hrs_claimed.toFixed(5)}
</Table.Summary.Cell>
<Table.Summary.Cell>
{summary.difference.toFixed(5)}
</Table.Summary.Cell>
</Table.Summary.Row>
)}
/>
</Card>
</Col>
{convertedLines && convertedLines.length > 0 && (
<Col span={24}>
<Card title={t("jobs.labels.convertedtolabor")}>
<Table
columns={convertedTableCols}
rowKey="id"
pagination={false}
dataSource={convertedLines}
scroll={{
x: true,
}}
/>
</Card>
</Col>
)}
</Row>
);
}
export default connect(mapStateToProps, null)(PayrollLaborAllocationsTable);

View File

@@ -0,0 +1,81 @@
import {useMutation} from "@apollo/client";
import {Button, Card, Col, notification, Row, Table} from "antd";
import day from "../../utils/day";
import React from "react";
import {useTranslation} from "react-i18next";
import {UPDATE_PARTS_DISPATCH_LINE} from "../../graphql/parts-dispatch.queries";
import {DateTimeFormatter} from "../../utils/DateFormatter";
export default function PartsDispatchExpander({dispatch, job}) {
const {t} = useTranslation();
const [updateDispatchLine] = useMutation(UPDATE_PARTS_DISPATCH_LINE);
const handleAccept = async ({partsDispatchLineId}) => {
const accepted_at = day();
const result = await updateDispatchLine({
variables: {id: partsDispatchLineId, line: {accepted_at}},
optimisticResponse: {
update_parts_dispatch_lines_by_pk: {
accepted_at,
id: partsDispatchLineId,
},
},
});
if (result.errors) {
notification.open({
type: "error",
message: t("parts_dispatch.errors.accepting", {
error: JSON.stringify(result.errors),
}),
});
}
};
const columns = [
{
title: t("joblines.fields.part_qty"),
dataIndex: "quantity",
key: "quantity",
width: "10%",
//sorter: (a, b) => alphaSort(a.number, b.number),
},
{
title: t("joblines.fields.line_desc"),
dataIndex: "joblineid",
key: "joblineid",
//sorter: (a, b) => alphaSort(a.number, b.number),
render: (text, record) => record.jobline.line_desc,
},
{
title: t("parts_dispatch_lines.fields.accepted_at"),
dataIndex: "accepted_at",
key: "accepted_at",
width: "20%",
//sorter: (a, b) => alphaSort(a.number, b.number),
render: (text, record) =>
record.accepted_at ? (
<DateTimeFormatter>{record.accepted_at}</DateTimeFormatter>
) : (
<Button
onClick={() => handleAccept({partsDispatchLineId: record.id})}
>
{t("parts_dispatch.actions.accept")}
</Button>
),
},
];
return (
<Card>
<Row gutter={[16, 16]}>
<Col span={24}>
<Table
rowKey={"id"}
dataSource={dispatch.parts_dispatch_lines}
columns={columns}
/>
</Col>
</Row>
</Card>
);
}

View File

@@ -0,0 +1,152 @@
import {MinusCircleTwoTone, PlusCircleTwoTone, SyncOutlined,} from "@ant-design/icons";
import {Button, Card, Input, Space, Table} from "antd";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectJobReadOnly} from "../../redux/application/application.selectors";
import {selectBodyshop} from "../../redux/user/user.selectors";
import {TemplateList} from "../../utils/TemplateConstants";
import {alphaSort} from "../../utils/sorters";
import PartsDispatchExpander from "../parts-dispatch-expander/parts-dispatch-expander.component";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({});
export function PartDispatchTableComponent({
bodyshop,
jobRO,
job,
billsQuery,
handleOnRowClick,
}) {
const {t} = useTranslation();
const [state, setState] = useState({
sortedInfo: {},
});
// const search = queryString.parse(useLocation().search);
// const selectedBill = search.billid;
const [searchText, setSearchText] = useState("");
const Templates = TemplateList("job_special");
const {refetch} = billsQuery;
const recordActions = (record) => (
<Space wrap>
<PrintWrapperComponent
templateObject={{
name: Templates.parts_dispatch.key,
variables: {id: record.id},
}}
/>
</Space>
);
const columns = [
{
title: t("parts_dispatch.fields.number"),
dataIndex: "number",
key: "number",
sorter: (a, b) => alphaSort(a.number, b.number),
width: "10%",
sortOrder:
state.sortedInfo.columnKey === "number" && state.sortedInfo.order,
},
{
title: t("timetickets.fields.employee"),
dataIndex: "employeeid",
key: "employeeid",
sorter: (a, b) => alphaSort(a.employeeid, b.employeeid),
sortOrder:
state.sortedInfo.columnKey === "employeeid" && state.sortedInfo.order,
render: (text, record) => {
const e = bodyshop.employees.find((e) => e.id === record.employeeid);
return `${e?.first_name || ""} ${e?.last_name || ""}`.trim();
},
},
{
title: t("parts_dispatch.fields.percent_accepted"),
dataIndex: "percent_accepted",
key: "percent_accepted",
render: (text, record) =>
record.parts_dispatch_lines.length > 0
? `
${(
(record.parts_dispatch_lines.filter((l) => l.accepted_at)
.length /
record.parts_dispatch_lines.length) *
100
).toFixed(0)}%`
: "0%",
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
width: "10%",
render: (text, record) => recordActions(record, true),
},
];
const handleTableChange = (pagination, filters, sorter) => {
setState({...state, filteredInfo: filters, sortedInfo: sorter});
};
return (
<Card
title={t("parts_dispatch.labels.parts_dispatch")}
extra={
<Space wrap>
<Button onClick={() => refetch()}>
<SyncOutlined/>
</Button>
<Input.Search
placeholder={t("general.labels.search")}
value={searchText}
onChange={(e) => {
e.preventDefault();
setSearchText(e.target.value);
}}
/>
</Space>
}
>
<Table
loading={billsQuery.loading}
scroll={{
x: true, // y: "50rem"
}}
expandable={{
expandedRowRender: (record) => (
<PartsDispatchExpander dispatch={record} job={job}/>
),
rowExpandable: (record) => true,
expandIcon: ({expanded, onExpand, record}) =>
expanded ? (
<MinusCircleTwoTone onClick={(e) => onExpand(record, e)}/>
) : (
<PlusCircleTwoTone onClick={(e) => onExpand(record, e)}/>
),
}}
columns={columns}
rowKey="id"
dataSource={billsQuery.data ? billsQuery.data.parts_dispatch : []}
onChange={handleTableChange}
/>
</Card>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(PartDispatchTableComponent);

View File

@@ -5,10 +5,7 @@ import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {INSERT_EXPORT_LOG} from "../../graphql/accounting.queries";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -37,16 +34,16 @@ export function BillMarkSelectedExported({
const [open, setOpen] = useState(false);
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
const [updateBill] = useMutation(gql`
mutation UPDATE_BILL($billIds: [uuid!]!) {
update_bills(where: { id: { _in: $billIds } }, _set: { exported: true }) {
returning {
id
exported
exported_at
mutation UPDATE_BILL($billIds: [uuid!]!) {
update_bills(where: { id: { _in: $billIds } }, _set: { exported: true }) {
returning {
id
exported
exported_at
}
}
}
}
}
`);
`);
const handleUpdate = async () => {
setLoading(true);

View File

@@ -1,12 +1,5 @@
import {useMutation, useQuery} from "@apollo/client";
import {
Button,
Descriptions,
InputNumber,
Modal,
Space,
notification,
} from "antd";
import {Button, Descriptions, InputNumber, Modal, notification, Space,} from "antd";
import axios from "axios";
import dayjs from "../../utils/day";
import React, {useState} from "react";

View File

@@ -5,10 +5,7 @@ import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {INSERT_EXPORT_LOG} from "../../graphql/accounting.queries";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -38,18 +35,18 @@ export function PaymentMarkSelectedExported({
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
const [updatePayments] = useMutation(gql`
mutation UPDATE_PAYMENTS($paymentIds: [uuid!]!, $exportedat: timestamptz!) {
update_payments(
where: { id: { _in: $paymentIds } }
_set: { exportedat: $exportedat }
) {
returning {
id
exportedat
mutation UPDATE_PAYMENTS($paymentIds: [uuid!]!, $exportedat: timestamptz!) {
update_payments(
where: { id: { _in: $paymentIds } }
_set: { exportedat: $exportedat }
) {
returning {
id
exportedat
}
}
}
}
}
`);
`);
const handleUpdate = async () => {
setLoading(true);

View File

@@ -1,3 +1,4 @@
import {useSplitTreatments} from "@splitsoftware/splitio-react";
import {Card, Col, Input, Row, Space, Typography} from "antd";
import _ from "lodash";
import React, {useState} from "react";
@@ -24,8 +25,14 @@ export function PrintCenterJobsComponent({printCenterModal, bodyshop}) {
const {id: jobId, job} = printCenterModal.context;
const tempList = TemplateList("job", {});
const {t} = useTranslation();
const {treatments: {Enhanced_Payroll}} = useSplitTreatments({
attributes: {},
names: ["Enhanced_Payroll"],
splitKey: bodyshop.imexshopid,
});
const JobsReportsList =
const Templates =
bodyshop.cdk_dealerid === null && bodyshop.pbs_serialnumber === null
? Object.keys(tempList)
.map((key) => {
@@ -52,7 +59,26 @@ export function PrintCenterJobsComponent({printCenterModal, bodyshop}) {
bodyshop.region_config.includes(Object.keys(temp.regions)) ===
true)
);
const JobsReportsList =
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 filteredJobsReportsList =
search !== ""
? JobsReportsList.filter((r) =>

View File

@@ -9,9 +9,11 @@ export default function PrintWrapperComponent({
children,
id,
emailOnly = false,
disabled,
}) {
const [loading, setLoading] = useState(false);
const handlePrint = async (type) => {
if (disabled) return;
setLoading(true);
await GenerateDocument(templateObject, messageObject, type, id);
setLoading(false);
@@ -20,8 +22,18 @@ export default function PrintWrapperComponent({
return (
<Space>
{children || null}
{!emailOnly && <PrinterFilled onClick={() => handlePrint("p")}/>}
<MailFilled onClick={() => handlePrint("e")}/>
{!emailOnly && (
<PrinterFilled
disabled={disabled}
onClick={() => handlePrint("p")}
style={{cursor: disabled ? "not-allowed" : null}}
/>
)}
<MailFilled
disabled={disabled}
onClick={() => handlePrint("e")}
style={{cursor: disabled ? "not-allowed" : null}}
/>
{loading && <Spin/>}
</Space>
);

View File

@@ -1,14 +1,5 @@
import {useMutation} from "@apollo/client";
import {
Button,
Card,
Col,
Form,
notification,
Popover,
Row,
Switch,
} from "antd";
import {Button, Card, Col, Form, notification, Popover, Row, Switch,} from "antd";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {UPDATE_KANBAN_SETTINGS} from "../../graphql/user.queries";

View File

@@ -25,6 +25,8 @@ import ProductionListColumnNote from "./production-list-columns.productionnote.c
import ProductionListColumnCategory from "./production-list-columns.status.category";
import ProductionListColumnStatus from "./production-list-columns.status.component";
import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component";
import {store} from "../../redux/store";
import {setModalContext} from "../../redux/modals/modals.actions";
const r = ({technician, state, activeStatuses, data, bodyshop}) => {
return [
@@ -39,6 +41,29 @@ const r = ({technician, state, activeStatuses, data, bodyshop}) => {
</Link>
),
},
{
title: i18n.t("timetickets.actions.claimtasks"),
dataIndex: "claimtasks",
key: "claimtasks",
ellipsis: true,
render: (text, record) => (
<div
onClick={() => {
store.dispatch(
setModalContext({
context: {
actions: {},
context: {jobid: record.id},
},
modal: "timeTicketTask",
})
);
}}
>
{i18n.t("timetickets.actions.claimtasks")}
</div>
),
},
{
title: i18n.t("jobs.fields.ro_number"),
dataIndex: "ro_number",

View File

@@ -1,15 +1,6 @@
import {DeleteFilled, PlusCircleFilled} from "@ant-design/icons";
import {useMutation} from "@apollo/client";
import {
Button,
Col,
notification,
Popover,
Row,
Select,
Space,
Spin,
} from "antd";
import {Button, Col, notification, Popover, Row, Select, Space, Spin,} from "antd";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";

View File

@@ -1,7 +1,7 @@
import {useMutation} from "@apollo/client";
import {Button, Card, Dropdown, Form, Input, notification, Space} from "antd";
import dayjs from "../../utils/day";
import React, {useState, useEffect} from "react";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";

View File

@@ -98,7 +98,7 @@ export function ProductionListDetail({
/>
}
placement="right"
width={"33%"}
width={"50%"}
onClose={handleClose}
open={selected}
>

View File

@@ -1,4 +1,5 @@
import {useLazyQuery} from "@apollo/client";
import {useSplitTreatments} from "@splitsoftware/splitio-react";
import {Button, Card, Col, DatePicker, Form, Input, Radio, Row, Typography,} from "antd";
import _ from "lodash";
import dayjs from "../../utils/day";
@@ -9,6 +10,7 @@ import {createStructuredSelector} from "reselect";
import {QUERY_ACTIVE_EMPLOYEES} from "../../graphql/employees.queries";
import {QUERY_ALL_VENDORS} from "../../graphql/vendors.queries";
import {selectReportCenter} from "../../redux/modals/modals.selectors";
import {selectBodyshop} from "../../redux/user/user.selectors";
import DatePIckerRanges from "../../utils/DatePickerRanges";
import {GenerateDocument} from "../../utils/RenderTemplate";
import {TemplateList} from "../../utils/TemplateConstants";
@@ -18,6 +20,7 @@ import "./report-center-modal.styles.scss";
const mapStateToProps = createStructuredSelector({
reportCenterModal: selectReportCenter,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
@@ -27,16 +30,39 @@ export default connect(
mapDispatchToProps
)(ReportCenterModalComponent);
export function ReportCenterModalComponent({reportCenterModal}) {
export function ReportCenterModalComponent({reportCenterModal, bodyshop}) {
const [form] = Form.useForm();
const [search, setSearch] = useState("");
const {treatments: {Enhanced_Payroll}} = useSplitTreatments({
attributes: {},
names: ["Enhanced_Payroll"],
splitKey: bodyshop.imexshopid,
});
const [loading, setLoading] = useState(false);
const {t} = useTranslation();
const Templates = TemplateList("report_center");
const ReportsList = Object.keys(Templates).map((key) => {
return Templates[key];
});
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 {open} = reportCenterModal;
const [callVendorQuery, {data: vendorData, called: vendorCalled}] =

View File

@@ -1,13 +1,4 @@
import {
Card,
Col,
Form,
Row,
Space,
Statistic,
Switch,
Typography,
} from "antd";
import {Card, Col, Form, Row, Space, Statistic, Switch, Typography,} from "antd";
import dayjs from "../../utils/day";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";

View File

@@ -26,6 +26,7 @@ import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.c
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import ShopEmployeeAddVacation from "./shop-employees-add-vacation.component";
import queryString from "query-string";
import {useSplitTreatments} from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -47,6 +48,12 @@ export function ShopEmployeesFormComponent({bodyshop}) {
nextFetchPolicy: "network-only",
});
const {treatments: {Enhanced_Payroll}} = useSplitTreatments({
attributes: {},
names: ["Enhanced_Payroll"],
splitKey: bodyshop.imexshopid,
});
const client = useApolloClient();
useEffect(() => {
if (data && data.employees_by_pk) form.setFieldsValue(data.employees_by_pk);
@@ -352,7 +359,7 @@ export function ShopEmployeesFormComponent({bodyshop}) {
{t("timetickets.labels.shift")}
</Select.Option>
{bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
{bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || Enhanced_Payroll.treatment === "on"
? CiecaSelect(false, true)
: bodyshop.md_responsibility_centers.costs.map(
(c) => (

Some files were not shown because too many files have changed in this diff Show More