Merge branch 'rome/master' into feature/payroll

This commit is contained in:
Patrick Fic
2023-10-16 12:10:51 -07:00
122 changed files with 11500 additions and 13146 deletions

View File

@@ -132,6 +132,57 @@ jobs:
to: "s3://rome-online-production/" to: "s3://rome-online-production/"
- jira/notify - jira/notify
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
echo ${HASURA_TEST_SECRET}
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:16.15.0
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- restore_cache:
name: Restore Yarn Package Cache
keys:
- yarn-packages-{{ checksum "yarn.lock" }}
- run:
name: Install Dependencies
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
- save_cache:
name: Save Yarn Package Cache
key: yarn-packages-{{ checksum "yarn.lock" }}
paths:
- ~/.cache/yarn
- run: yarn run build:test
- aws-s3/sync:
from: build
to: "s3://rome-online-test/"
- jira/notify
test-hasura-migrate: test-hasura-migrate:
docker: docker:
- image: cimg/node:16.15.0 - image: cimg/node:16.15.0
@@ -250,6 +301,15 @@ workflows:
filters: filters:
branches: branches:
only: test 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: #- admin-app-build:
#filters: #filters:
#branches: #branches:

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,14 @@
REACT_APP_GRAPHQL_ENDPOINT=https://db.test.bodyshop.app/v1/graphql REACT_APP_GRAPHQL_ENDPOINT=https://db.test.romeonline.io/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.bodyshop.app/v1/graphql REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.romeonline.io/v1/graphql
REACT_APP_GA_CODE=231099835 REACT_APP_GA_CODE=231103507
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_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_API=https://api.cloudinary.com/v1_1/bodyshop
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
REACT_APP_CLOUDINARY_API_KEY=473322739956866 REACT_APP_CLOUDINARY_API_KEY=473322739956866
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250 REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BN2GcDPjipR5MTEosO5dT4CfQ3cmrdBIsI4juoOQrRijn_5aRiHlwj1mlq0W145mOusx6xynEKl_tvYJhpCc9lo' REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=https://api.romeonline.io/ REACT_APP_AXIOS_BASE_API_URL=https://api.test.romeonline.io/
REACT_APP_REPORTS_SERVER_URL=https://reports.romeonline.io REACT_APP_REPORTS_SERVER_URL=https://reports.test.romeonline.io
REACT_APP_IS_TEST=true REACT_APP_IS_TEST=true
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc

View File

@@ -8,25 +8,6 @@
<meta name="description" content="Rome Online" /> <meta name="description" content="Rome Online" />
<!-- <link rel="apple-touch-icon" href="logo192.png" /> --> <!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
<link rel="apple-touch-icon" href="logo192.png" /> <link rel="apple-touch-icon" href="logo192.png" />
<script type="text/javascript">
var $zoho = $zoho || {};
$zoho.salesiq = $zoho.salesiq || {
widgetcode:
"2ee4b2212fbdb380fb1e5e612f1e2dd7fe52032bee013140e27458e960add8e65b3cc65a44e7ecddabee40ced28dcfbd",
values: {},
ready: function () {},
};
var d = document;
s = d.createElement("script");
s.type = "text/javascript";
s.id = "zsiqscript";
s.defer = true;
s.src = "https://salesiq.zoho.com/widget";
t = d.getElementsByTagName("script")[0];
t.parentNode.insertBefore(s, t);
d.write("<div id='zsiqwidget'></div>");
</script>
<script> <script>
!(function () { !(function () {
"use strict"; "use strict";

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -320,12 +320,12 @@ function BillEnterModalContainer({
}); });
if (enterAgain) { if (enterAgain) {
form.resetFields(); // form.resetFields();
form.resetFields();
form.setFieldsValue({ form.setFieldsValue({
...formValues, ...formValues,
billlines: [], billlines: [],
}); });
form.resetFields();
} else { } else {
toggleModalVisible(); toggleModalVisible();
} }

View File

@@ -1,6 +1,6 @@
import Icon, { UploadOutlined } from "@ant-design/icons"; import Icon, { UploadOutlined } from "@ant-design/icons";
import { useApolloClient } from "@apollo/client"; import { useApolloClient } from "@apollo/client";
import { MdOpenInNew } from "react-icons/md"; import { useTreatments } from "@splitsoftware/splitio-react";
import { import {
Alert, Alert,
Divider, Divider,
@@ -12,14 +12,17 @@ import {
Switch, Switch,
Upload, Upload,
} from "antd"; } from "antd";
import moment from "moment";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { MdOpenInNew } from "react-icons/md";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { CHECK_BILL_INVOICE_NUMBER } from "../../graphql/bills.queries"; import { CHECK_BILL_INVOICE_NUMBER } from "../../graphql/bills.queries";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
import FormDatePicker from "../form-date-picker/form-date-picker.component"; import FormDatePicker from "../form-date-picker/form-date-picker.component";
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component"; import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component";
@@ -28,8 +31,6 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component"; import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import BillFormLines from "./bill-form.lines.component"; import BillFormLines from "./bill-form.lines.component";
import { CalculateBillTotal } from "./bill-form.totals.utility"; import { CalculateBillTotal } from "./bill-form.totals.utility";
import { useTreatments } from "@splitsoftware/splitio-react";
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -58,6 +59,11 @@ export function BillFormComponent({
{}, {},
bodyshop.imexshopid bodyshop.imexshopid
); );
const { ClosingPeriod } = useTreatments(
["ClosingPeriod"],
{},
bodyshop.imexshopid
);
const handleVendorSelect = (props, opt) => { const handleVendorSelect = (props, opt) => {
setDiscount(opt.discount); setDiscount(opt.discount);
@@ -259,6 +265,37 @@ export function BillFormComponent({
required: true, required: true,
//message: t("general.validation.required"), //message: t("general.validation.required"),
}, },
({ getFieldValue }) => ({
validator(rule, value) {
if (
ClosingPeriod.treatment === "on" &&
bodyshop.accountingconfig.ClosingPeriod
) {
if (
moment(value)
.startOf("day")
.isSameOrAfter(
moment(
bodyshop.accountingconfig.ClosingPeriod[0]
).startOf("day")
) &&
moment(value)
.startOf("day")
.isSameOrBefore(
moment(
bodyshop.accountingconfig.ClosingPeriod[1]
).endOf("day")
)
) {
return Promise.resolve();
} else {
return Promise.reject(t("bills.validation.closingperiod"));
}
} else {
return Promise.resolve();
}
},
}),
]} ]}
> >
<FormDatePicker disabled={disabled} /> <FormDatePicker disabled={disabled} />
@@ -327,13 +364,15 @@ export function BillFormComponent({
)} )}
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow> <LayoutFormRow>
<Form.Item {
span={3} // <Form.Item
label={t("bills.fields.federal_tax_rate")} // span={3}
name="federal_tax_rate" // label={t("bills.fields.federal_tax_rate")}
> // name="federal_tax_rate"
<CurrencyInput min={0} disabled={disabled} /> // >
</Form.Item> // <CurrencyInput min={0} disabled={disabled} />
// </Form.Item>
}
<Form.Item <Form.Item
span={3} span={3}
label={t("bills.fields.state_tax_rate")} label={t("bills.fields.state_tax_rate")}
@@ -341,13 +380,15 @@ export function BillFormComponent({
> >
<CurrencyInput min={0} disabled={disabled} /> <CurrencyInput min={0} disabled={disabled} />
</Form.Item> </Form.Item>
<Form.Item {
span={3} // <Form.Item
label={t("bills.fields.local_tax_rate")} // span={3}
name="local_tax_rate" // label={t("bills.fields.local_tax_rate")}
> // name="local_tax_rate"
<CurrencyInput min={0} /> // >
</Form.Item> // <CurrencyInput min={0} />
// </Form.Item>
}
<Form.Item shouldUpdate span={15}> <Form.Item shouldUpdate span={15}>
{() => { {() => {
const values = form.getFieldsValue([ const values = form.getFieldsValue([
@@ -373,21 +414,25 @@ export function BillFormComponent({
value={totals.subtotal.toFormat()} value={totals.subtotal.toFormat()}
precision={2} precision={2}
/> />
<Statistic {
title={t("bills.labels.federal_tax")} // <Statistic
value={totals.federalTax.toFormat()} // title={t("bills.labels.federal_tax")}
precision={2} // value={totals.federalTax.toFormat()}
/> // precision={2}
// />
}
<Statistic <Statistic
title={t("bills.labels.state_tax")} title={t("bills.labels.state_tax")}
value={totals.stateTax.toFormat()} value={totals.stateTax.toFormat()}
precision={2} precision={2}
/> />
<Statistic {
title={t("bills.labels.local_tax")} // <Statistic
value={totals.localTax.toFormat()} // title={t("bills.labels.local_tax")}
precision={2} // value={totals.localTax.toFormat()}
/> // precision={2}
// />
}
<Statistic <Statistic
title={t("bills.labels.entered_total")} title={t("bills.labels.entered_total")}
value={totals.enteredTotal.toFormat()} value={totals.enteredTotal.toFormat()}

View File

@@ -458,21 +458,21 @@ export function BillEnterModalLinesComponent({
</Form.Item> </Form.Item>
), ),
}, },
{ // {
title: t("billlines.fields.federal_tax_applicable"), // title: t("billlines.fields.federal_tax_applicable"),
dataIndex: "applicable_taxes.federal", // dataIndex: "applicable_taxes.federal",
editable: true, // editable: true,
formItemProps: (field) => { // formItemProps: (field) => {
return { // return {
key: `${field.index}fedtax`, // key: `${field.index}fedtax`,
valuePropName: "checked", // valuePropName: "checked",
// initialValue: true, // // initialValue: true,
name: [field.name, "applicable_taxes", "federal"], // name: [field.name, "applicable_taxes", "federal"],
}; // };
}, // },
formInput: (record, index) => <Switch disabled={disabled} />, // formInput: (record, index) => <Switch disabled={disabled} />,
}, // },
{ {
title: t("billlines.fields.state_tax_applicable"), title: t("billlines.fields.state_tax_applicable"),
dataIndex: "applicable_taxes.state", dataIndex: "applicable_taxes.state",
@@ -487,20 +487,20 @@ export function BillEnterModalLinesComponent({
}, },
formInput: (record, index) => <Switch disabled={disabled} />, formInput: (record, index) => <Switch disabled={disabled} />,
}, },
{ // {
title: t("billlines.fields.local_tax_applicable"), // title: t("billlines.fields.local_tax_applicable"),
dataIndex: "applicable_taxes.local", // dataIndex: "applicable_taxes.local",
editable: true, // editable: true,
formItemProps: (field) => { // formItemProps: (field) => {
return { // return {
key: `${field.index}localtax`, // key: `${field.index}localtax`,
valuePropName: "checked", // valuePropName: "checked",
name: [field.name, "applicable_taxes", "local"], // name: [field.name, "applicable_taxes", "local"],
}; // };
}, // },
formInput: (record, index) => <Switch disabled={disabled} />, // formInput: (record, index) => <Switch disabled={disabled} />,
}, // },
{ {
title: t("general.labels.actions"), title: t("general.labels.actions"),

View File

@@ -1,21 +1,40 @@
import React, { useCallback, useEffect } from "react"; import { DeleteFilled } from "@ant-design/icons";
import { useLazyQuery, useMutation } from "@apollo/client";
import {
Button,
Card,
Col,
Form,
Input,
Row,
Space,
Spin,
Statistic,
notification,
} from "antd";
import axios from "axios"; import axios from "axios";
import { useTranslation } from "react-i18next";
import { Button, Card, Form, Input, InputNumber, Row, Select } from "antd";
import moment from "moment"; import moment from "moment";
import { useMutation, useQuery } from "@apollo/client"; import React, { useState } from "react";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import { useTranslation } from "react-i18next";
import JobSearchSelectComponent from "../job-search-select/job-search-select.component"; import { connect } from "react-redux";
import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries"; import { createStructuredSelector } from "reselect";
import { import {
INSERT_PAYMENT_RESPONSE, INSERT_PAYMENT_RESPONSE,
QUERY_RO_AND_OWNER_BY_JOB_PK, QUERY_RO_AND_OWNER_BY_JOB_PKS,
} from "../../graphql/payment_response.queries"; } from "../../graphql/payment_response.queries";
import DataLabel from "../data-label/data-label.component"; import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries";
import { insertAuditTrail } from "../../redux/application/application.actions"; import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { connect } from "react-redux";
import { toggleModalVisible } from "../../redux/modals/modals.actions"; import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectCardPayment } from "../../redux/modals/modals.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
const mapStateToProps = createStructuredSelector({
cardPaymentModal: selectCardPayment,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation }) =>
@@ -25,253 +44,331 @@ const mapDispatchToProps = (dispatch) => ({
const CardPaymentModalComponent = ({ const CardPaymentModalComponent = ({
bodyshop, bodyshop,
context, cardPaymentModal,
toggleModalVisible, toggleModalVisible,
insertAuditTrail, insertAuditTrail,
}) => { }) => {
const { context } = cardPaymentModal;
const [form] = Form.useForm(); const [form] = Form.useForm();
const amount = Form.useWatch("amount", form);
const payer = Form.useWatch("payer", form); const [loading, setLoading] = useState(false);
const jobid = Form.useWatch("jobid", form);
const [insertPayment] = useMutation(INSERT_NEW_PAYMENT); const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE); const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
const { t } = useTranslation(); const { t } = useTranslation();
const { data, refetch } = useQuery(QUERY_RO_AND_OWNER_BY_JOB_PK, { const [, { data, refetch, queryLoading }] = useLazyQuery(
variables: { jobid: context?.jobid ?? "" }, QUERY_RO_AND_OWNER_BY_JOB_PKS,
}); {
variables: { jobids: [context.jobid] },
skip: true,
}
);
const nonApproval = useCallback( console.log("🚀 ~ file: card-payment-modal.component..jsx:61 ~ data:", data);
async (response) => { //Initialize the intellipay window.
const SetIntellipayCallbackFunctions = () => {
console.log("*** Set IntelliPay callback functions.");
window.intellipay.runOnClose(() => {
//window.intellipay.initialize();
});
window.intellipay.runOnApproval(async function (response) {
console.warn("*** Running On Approval Script ***");
form.setFieldValue("paymentResponse", response);
form.submit();
});
window.intellipay.runOnNonApproval(async function (response) {
// Mutate unsuccessful payment // Mutate unsuccessful payment
const { payments } = form.getFieldsValue();
await insertPaymentResponse({ await insertPaymentResponse({
variables: { variables: {
paymentResponse: { paymentResponse: payments.map((payment) => ({
amount: response.amount, amount: payment.amount,
bodyshopid: bodyshop.id, bodyshopid: bodyshop.id,
jobid: jobid || context.jobid, jobid: payment.jobid,
declinereason: response.declinereason, declinereason: response.declinereason,
ext_paymentid: response.paymentid.toString(), ext_paymentid: response.paymentid.toString(),
successful: false, successful: false,
response, response,
}, })),
}, },
}); });
// Insert failed payment to audit trail payments.forEach((payment) =>
insertAuditTrail({ insertAuditTrail({
jobid: jobid || context?.jobid, jobid: payment.jobid,
operation: AuditTrailMapping.failedpayment(), operation: AuditTrailMapping.failedpayment(),
}); })
}, );
[bodyshop, context, insertAuditTrail, insertPaymentResponse, jobid] });
); };
const initIntellipayFunctions = useCallback(() => {
if (window.intellipay !== undefined && typeof jobid !== "undefined") {
console.log("intellipay init functions");
window.intellipay.runOnClose(() => {
window.intellipay.initialize();
});
window.intellipay.runOnApproval(async function (response) {
form.setFieldValue("paymentResponse", response);
form.submit();
toggleModalVisible();
});
window.intellipay.runOnNonApproval(nonApproval);
}
}, [form, jobid, nonApproval, toggleModalVisible]);
const initJobId = useCallback(() => {
if (context?.jobid) {
form.setFieldValue("jobid", context.jobid);
}
form.setFieldValue("payer", t("payments.labels.customer"));
}, [context?.jobid, form, t]);
useEffect(() => {
initJobId();
axios
.post("/intellipay/lightbox_credentials", { bodyshop })
.then((response) => {
var rg = document.createRange();
let node = rg.createContextualFragment(response.data);
document.documentElement.appendChild(node);
window.intellipay.initialize();
initIntellipayFunctions();
});
function handleEvents(...props) {
const operation = props[0].data.operation;
if (operation === "updateform") {
props[0].stopImmediatePropagation();
}
}
window.addEventListener("message", handleEvents, false);
return () => window.removeEventListener("message", handleEvents, false);
}, [bodyshop, initJobId, initIntellipayFunctions]);
const handleFinish = async (values) => { const handleFinish = async (values) => {
const paymentResult = await insertPayment({ try {
variables: { await insertPayment({
paymentInput: { variables: {
amount: values.amount, paymentInput: values.payments.map((payment) => ({
transactionid: values.paymentResponse.receiptelements.transid, amount: payment.amount,
payer: values.payer, transactionid: (values.paymentResponse.paymentid || "").toString(),
type: values.paymentResponse.cardType, payer: t("payments.labels.customer"),
jobid: values.jobid, type: values.paymentResponse.cardbrand,
date: moment(Date.now()), jobid: payment.jobid,
}, date: moment(Date.now()),
}, payment_responses: {
update(cache, { data }) { data: [
cache.modify({ {
id: cache.identify({ id: jobid, __typename: "jobs" }), amount: payment.amount,
fields: { bodyshopid: bodyshop.id,
payments(payments) {
return [...data.insert_payments.returning, ...payments];
},
},
});
},
});
await insertPaymentResponse({ jobid: payment.jobid,
variables: { declinereason: values.paymentResponse.declinereason,
paymentResponse: { ext_paymentid: values.paymentResponse.paymentid.toString(),
amount: values.amount, successful: true,
bodyshopid: bodyshop.id, response: values.paymentResponse,
paymentid: paymentResult.data.insert_payments.returning[0].id, },
jobid: values.jobid, ],
declinereason: values.paymentResponse.declinereason, },
ext_paymentid: values.paymentResponse.paymentid.toString(), })),
successful: true,
response: values.paymentResponse,
}, },
}, refetchQueries: ["GET_JOB_BY_PK"],
}); });
toggleModalVisible();
} catch (error) {
console.error(error);
notification.open({
type: "error",
message: t("payments.errors.inserting", { error: error.message }),
});
} finally {
setLoading(false);
}
};
const handleIntelliPayCharge = async () => {
setLoading(true);
//Validate
try {
await form.validateFields();
} catch (error) {
setLoading(false);
return;
}
try {
const response = await axios.post("/intellipay/lightbox_credentials", {
bodyshop,
refresh: !!window.intellipay,
});
if (window.intellipay) {
// eslint-disable-next-line no-eval
eval(response.data);
SetIntellipayCallbackFunctions();
window.intellipay.autoOpen();
} else {
var rg = document.createRange();
let node = rg.createContextualFragment(response.data);
document.documentElement.appendChild(node);
SetIntellipayCallbackFunctions();
window.intellipay.isAutoOpen = true;
window.intellipay.initialize();
}
} catch (error) {
notification.open({
type: "error",
message: t("job_payments.notifications.error.openingip"),
});
setLoading(false);
}
}; };
return ( return (
<Card title="Card Payment"> <Card title="Card Payment">
<Form onFinish={handleFinish} form={form}> <Spin spinning={loading}>
<LayoutFormRow grow> <Form
<Form.Item onFinish={handleFinish}
name="jobid" form={form}
label={t("bills.fields.ro_number")} layout="vertical"
rules={[ initialValues={{
{ payments: context.jobid ? [{ jobid: context.jobid }] : [],
required: true, }}
// message: t("general.validation.required"), >
}, <Form.List name={["payments"]}>
]} {(fields, { add, remove, move }) => {
> return (
<JobSearchSelectComponent <div>
disabled={context?.jobid} {fields.map((field, index) => (
notExported={false} <Form.Item key={field.key}>
clm_no <Row gutter={[16, 16]}>
onChange={(e) => { <Col span={16}>
refetch({ jobid: e }); <Form.Item
}} key={`${index}jobid`}
/> label={t("jobs.fields.ro_number")}
</Form.Item> name={[field.name, "jobid"]}
</LayoutFormRow> rules={[
{
{/* Lighbox Input amount needs to be hidden */} required: true,
<Input //message: t("general.validation.required"),
className="ipayfield" },
data-ipayname="amount" ]}
type="hidden" >
value={amount} <JobSearchSelectComponent
hidden notExported={false}
/> clm_no
<Input />
className="ipayfield" </Form.Item>
data-ipayname="account" </Col>
type="hidden" <Col span={6}>
value={data?.jobs_by_pk.ro_number} <Form.Item
hidden key={`${index}amount`}
/> label={t("payments.fields.amount")}
<Input name={[field.name, "amount"]}
className="ipayfield" rules={[
data-ipayname="email" {
type="hidden" required: true,
value={data?.jobs_by_pk.owner.ownr_ea} //message: t("general.validation.required"),
hidden },
/> ]}
{/* Lightbox payment response when it is completed */} >
<Form.Item name="paymentResponse" hidden> <CurrencyFormItemComponent />
<Input type="hidden" value={amount} /> </Form.Item>
</Form.Item> </Col>
<Col span={2}>
<LayoutFormRow grow> <DeleteFilled
<Form.Item style={{ margin: "1rem" }}
label={t("payments.fields.payer")} onClick={() => {
name="payer" remove(field.name);
rules={[ }}
{ />
required: true, </Col>
// message: t("general.validation.required"), </Row>
}, </Form.Item>
]} ))}
> <Form.Item>
<Select> <Button
<Select.Option value={t("payments.labels.customer")}> type="dashed"
{t("payments.labels.customer")} onClick={() => {
</Select.Option> add();
<Select.Option value={t("payments.labels.insurance")}> }}
{t("payments.labels.insurance")} style={{ width: "100%" }}
</Select.Option> >
</Select> {t("general.actions.add")}
</Form.Item> </Button>
</Form.Item>
</div>
);
}}
</Form.List>
<Form.Item <Form.Item
label="Amount" shouldUpdate={(prevValues, curValues) =>
name="amount" prevValues.payments?.map((p) => p?.jobid).join() !==
rules={[ curValues.payments?.map((p) => p?.jobid).join()
{ }
required: true,
// message: t("general.validation.required"),
},
]}
> >
<InputNumber /> {() => {
console.log("Updating the owner info section.");
//If all of the job ids have been fileld in, then query and update the IP field.
const { payments } = form.getFieldsValue();
if (
payments?.length > 0 &&
payments?.filter((p) => p?.jobid).length === payments?.length
) {
console.log("**Calling refetch.");
refetch({ jobids: payments.map((p) => p.jobid) });
}
console.log(
"Acc info",
data,
payments && data && data.jobs.length > 0
? data.jobs.map((j) => j.ro_number).join(", ")
: null
);
return (
<>
<Input
className="ipayfield"
data-ipayname="account"
//type="hidden"
value={
payments && data && data.jobs.length > 0
? data.jobs.map((j) => j.ro_number).join(", ")
: null
}
hidden
/>
<Input
className="ipayfield"
data-ipayname="email"
// type="hidden"
value={
payments && data && data.jobs.length > 0
? data.jobs.filter((j) => j.ownr_ea)[0]?.ownr_ea
: null
}
hidden
/>
</>
);
}}
</Form.Item>
<Form.Item
shouldUpdate={(prevValues, curValues) =>
prevValues.payments?.map((p) => p?.amount).join() !==
curValues.payments?.map((p) => p?.amount).join()
}
>
{() => {
const { payments } = form.getFieldsValue();
const totalAmountToCharge = payments?.reduce((acc, val) => {
return acc + (val?.amount || 0);
}, 0);
return (
<Space style={{ float: "right" }}>
<Statistic
title="Amount To Charge"
value={totalAmountToCharge}
precision={2}
/>
<Input
className="ipayfield"
data-ipayname="amount"
//type="hidden"
value={totalAmountToCharge?.toFixed(2)}
hidden
/>
<Button
type="primary"
// data-ipayname="submit"
className="ipayfield"
loading={queryLoading || loading}
disabled={!(totalAmountToCharge > 0)}
onClick={handleIntelliPayCharge}
>
{t("job_payments.buttons.proceedtopayment")}
</Button>
</Space>
);
}}
</Form.Item> </Form.Item>
<Row justify="space-around"> {/* Lightbox payment response when it is completed */}
<Button <Form.Item name="paymentResponse" hidden>
type="primary" <Input type="hidden" />
data-ipayname="submit" </Form.Item>
className="ipayfield" </Form>
disabled={!amount || !payer || !jobid} </Spin>
>
{t("job_payments.buttons.proceedtopayment")}
</Button>
{context?.balance && (
<DataLabel
valueStyle={{
color: context?.balance.getAmount() !== 0 ? "red" : "green",
}}
label={t("payments.labels.balance")}
>
{context?.balance.toFormat()}
</DataLabel>
)}
</Row>
</LayoutFormRow>
</Form>
</Card> </Card>
); );
}; };
export default connect(null, mapDispatchToProps)(CardPaymentModalComponent); export default connect(
mapStateToProps,
mapDispatchToProps
)(CardPaymentModalComponent);

View File

@@ -22,7 +22,7 @@ function CardPaymentModalContainer({
toggleModalVisible, toggleModalVisible,
bodyshop, bodyshop,
}) { }) {
const { context, visible } = cardPaymentModal; const { visible } = cardPaymentModal;
const { t } = useTranslation(); const { t } = useTranslation();
const handleCancel = () => { const handleCancel = () => {
@@ -35,7 +35,7 @@ function CardPaymentModalContainer({
return ( return (
<Modal <Modal
visible={visible} open={visible}
onOk={handleOK} onOk={handleOK}
onCancel={handleCancel} onCancel={handleCancel}
footer={[ footer={[
@@ -43,10 +43,10 @@ function CardPaymentModalContainer({
{t("job_payments.buttons.goback")} {t("job_payments.buttons.goback")}
</Button>, </Button>,
]} ]}
width="60%" width="80%"
destroyOnClose destroyOnClose
> >
<CardPaymentModalComponent bodyshop={bodyshop} context={context} /> <CardPaymentModalComponent />
</Modal> </Modal>
); );
} }

View File

@@ -23,36 +23,40 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
const appt = []; // Flatten Data const appt = []; // Flatten Data
data.scheduled_in_today.forEach((item) => { data.scheduled_in_today.forEach((item) => {
var i = { if (item.job) {
canceled: item.canceled, var i = {
id: item.id, canceled: item.canceled,
alt_transport: item.job.alt_transport, id: item.id,
clm_no: item.job.clm_no, alt_transport: item.job.alt_transport,
jobid: item.job.jobid, clm_no: item.job.clm_no,
ins_co_nm: item.job.ins_co_nm, jobid: item.job.jobid,
iouparent: item.job.iouparent, ins_co_nm: item.job.ins_co_nm,
ownerid: item.job.ownerid, iouparent: item.job.iouparent,
ownr_co_nm: item.job.ownr_co_nm, ownerid: item.job.ownerid,
ownr_ea: item.job.ownr_ea, ownr_co_nm: item.job.ownr_co_nm,
ownr_fn: item.job.ownr_fn, ownr_ea: item.job.ownr_ea,
ownr_ln: item.job.ownr_ln, ownr_fn: item.job.ownr_fn,
ownr_ph1: item.job.ownr_ph1, ownr_ln: item.job.ownr_ln,
ownr_ph2: item.job.ownr_ph2, ownr_ph1: item.job.ownr_ph1,
production_vars: item.job.production_vars, ownr_ph2: item.job.ownr_ph2,
ro_number: item.job.ro_number, production_vars: item.job.production_vars,
suspended: item.job.suspended, ro_number: item.job.ro_number,
v_make_desc: item.job.v_make_desc, suspended: item.job.suspended,
v_model_desc: item.job.v_model_desc, v_make_desc: item.job.v_make_desc,
v_model_yr: item.job.v_model_yr, v_model_desc: item.job.v_model_desc,
v_vin: item.job.v_vin, v_model_yr: item.job.v_model_yr,
vehicleid: item.job.vehicleid, v_vin: item.job.v_vin,
note: item.note, vehicleid: item.job.vehicleid,
start: moment(item.start).format("hh:mm a"), note: item.note,
title: item.title, start: moment(item.start).format("hh:mm a"),
}; title: item.title,
appt.push(i); };
appt.push(i);
}
});
appt.sort(function (a, b) {
return new moment(a.start) - new moment(b.start);
}); });
appt.sort ( function (a, b) { return new Date(a.start) - new Date(b.start); });
const columns = [ const columns = [
{ {
@@ -182,7 +186,12 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
}; };
return ( return (
<Card title={t("dashboard.titles.scheduledintoday", {date: moment().startOf("day").format("MM/DD/YYYY")})} {...cardProps}> <Card
title={t("dashboard.titles.scheduledintoday", {
date: moment().startOf("day").format("MM/DD/YYYY"),
})}
{...cardProps}
>
<div style={{ height: "100%" }}> <div style={{ height: "100%" }}>
<Table <Table
onChange={handleTableChange} onChange={handleTableChange}

View File

@@ -18,7 +18,7 @@ export default function DataLabel({
<div {...props} style={{ display: "flex" }}> <div {...props} style={{ display: "flex" }}>
<div <div
style={{ style={{
flex: 2, // flex: 2,
marginRight: ".2rem", marginRight: ".2rem",
}} }}
> >

View File

@@ -40,22 +40,22 @@ class ErrorBoundary extends React.Component {
} }
handleErrorSubmit = () => { handleErrorSubmit = () => {
window.$crisp.push([ // window.$crisp.push([
"do", // "do",
"message:send", // "message:send",
[ // [
"text", // "text",
`I hit the following error: \n\n // `I hit the following error: \n\n
${this.state.error.message}\n\n // ${this.state.error.message}\n\n
${this.state.error.stack}\n\n // ${this.state.error.stack}\n\n
URL:${window.location} as ${this.props.currentUser.email} for ${ // URL:${window.location} as ${this.props.currentUser.email} for ${
this.props.bodyshop && this.props.bodyshop.name // 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** // const errorDescription = `**Please add relevant details about what you were doing before you encountered this issue**
// ---- // ----

View File

@@ -1,10 +1,9 @@
import { useTreatments } from "@splitsoftware/splitio-react";
import Icon, { import Icon, {
BankFilled, BankFilled,
BarChartOutlined, BarChartOutlined,
CarFilled, CarFilled,
ClockCircleFilled,
CheckCircleOutlined, CheckCircleOutlined,
ClockCircleFilled,
DashboardFilled, DashboardFilled,
DollarCircleFilled, DollarCircleFilled,
ExportOutlined, ExportOutlined,
@@ -26,6 +25,7 @@ import Icon, {
UnorderedListOutlined, UnorderedListOutlined,
UserOutlined, UserOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react";
import { Layout, Menu } from "antd"; import { Layout, Menu } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -97,6 +97,11 @@ function Header({
{}, {},
bodyshop && bodyshop.imexshopid bodyshop && bodyshop.imexshopid
); );
const { ImEXPay } = useTreatments(
["ImEXPay"],
{},
bodyshop && bodyshop.imexshopid
);
const { t } = useTranslation(); const { t } = useTranslation();
@@ -243,19 +248,20 @@ function Header({
> >
{t("menus.header.enterpayment")} {t("menus.header.enterpayment")}
</Menu.Item> </Menu.Item>
{/* TODO: Enter Card Payment */} {ImEXPay.treatment === "on" && (
<Menu.Item <Menu.Item
key="entercardpayments" key="entercardpayments"
onClick={() => { onClick={() => {
setCardPaymentContext({ setCardPaymentContext({
actions: {}, actions: {},
context: null, context: {},
}); });
}} }}
icon={<Icon component={FaCreditCard} />} icon={<Icon component={FaCreditCard} />}
> >
{t("menus.header.entercardpayment")} {t("menus.header.entercardpayment")}
</Menu.Item> </Menu.Item>
)}
<Menu.Divider key="div5" /> <Menu.Divider key="div5" />
<Menu.Item key="timetickets" icon={<FieldTimeOutlined />}> <Menu.Item key="timetickets" icon={<FieldTimeOutlined />}>
<Link to="/manage/timetickets"> <Link to="/manage/timetickets">
@@ -275,7 +281,11 @@ function Header({
onClick={() => { onClick={() => {
setTimeTicketContext({ setTimeTicketContext({
actions: {}, actions: {},
context: {}, context: {
created_by: currentUser.displayName
? currentUser.email.concat(" | ", currentUser.displayName)
: currentUser.email,
},
}); });
}} }}
> >
@@ -379,20 +389,12 @@ function Header({
<Menu.Item <Menu.Item
key="help" key="help"
onClick={() => { onClick={() => {
window.open("https://help.imex.online/", "_blank"); window.open("https://rometech.com/", "_blank");
}} }}
icon={<Icon component={QuestionCircleFilled} />} icon={<Icon component={QuestionCircleFilled} />}
> >
{t("menus.header.help")} {t("menus.header.help")}
</Menu.Item> </Menu.Item>
<Menu.Item
key="rescue"
onClick={() => {
window.open("https://imexrescue.com/", "_blank");
}}
>
{t("menus.header.rescueme")}
</Menu.Item>
<Menu.Item key="shiftclock"> <Menu.Item key="shiftclock">
<Link to="/manage/shiftclock">{t("menus.header.shiftclock")}</Link> <Link to="/manage/shiftclock">{t("menus.header.shiftclock")}</Link>
</Menu.Item> </Menu.Item>

View File

@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { SEARCH_VENDOR_AUTOCOMPLETE_WITH_ADDR } from "../../graphql/vendors.queries"; import { SEARCH_VENDOR_AUTOCOMPLETE_WITH_ADDR } from "../../graphql/vendors.queries";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { GenerateDocument } from "../../utils/RenderTemplate"; import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
@@ -13,13 +14,14 @@ import VendorSearchSelect from "../vendor-search-select/vendor-search-select.com
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
technician: selectTechnician,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) //setUserLanguage: language => dispatch(setUserLanguage(language))
}); });
export default connect(mapStateToProps, mapDispatchToProps)(Jobd3RdPartyModal); export default connect(mapStateToProps, mapDispatchToProps)(Jobd3RdPartyModal);
export function Jobd3RdPartyModal({ bodyshop, jobId, job }) { export function Jobd3RdPartyModal({ bodyshop, jobId, job, technician }) {
const [isModalVisible, setIsModalVisible] = useState(false); const [isModalVisible, setIsModalVisible] = useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
const [form] = Form.useForm(); const [form] = Form.useForm();
@@ -212,7 +214,9 @@ export function Jobd3RdPartyModal({ bodyshop, jobId, job }) {
]} ]}
> >
<Radio.Group> <Radio.Group>
<Radio value={"e"}>{t("parts_orders.labels.email")}</Radio> {!technician ? (
<Radio value={"e"}>{t("parts_orders.labels.email")}</Radio>
) : null}
<Radio value={"p"}>{t("parts_orders.labels.print")}</Radio> <Radio value={"p"}>{t("parts_orders.labels.print")}</Radio>
</Radio.Group> </Radio.Group>
</Form.Item> </Form.Item>

View File

@@ -29,11 +29,11 @@ import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import DataLabel from "../data-label/data-label.component"; import DataLabel from "../data-label/data-label.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component"; import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
import ScheduleAtChange from "./job-at-change.component"; import ScheduleAtChange from "./job-at-change.component";
import ScheduleEventColor from "./schedule-event.color.component"; import ScheduleEventColor from "./schedule-event.color.component";
import ScheduleEventNote from "./schedule-event.note.component"; import ScheduleEventNote from "./schedule-event.note.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -208,46 +208,56 @@ export function ScheduleEventComponent({
<Button>{t("appointments.actions.sendreminder")}</Button> <Button>{t("appointments.actions.sendreminder")}</Button>
</Dropdown> </Dropdown>
) : null} ) : null}
<Popover {event.arrived ? (
trigger="click"
disabled={event.arrived}
content={
<Form
layout="vertical"
onFinish={({ lost_sale_reason }) => {
handleCancel({ id: event.id, lost_sale_reason });
}}
>
<Form.Item
name="lost_sale_reason"
label={t("jobs.fields.lost_sale_reason")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
label: lsr,
value: lsr,
}))}
/>
</Form.Item>
<Button htmlType="submit">
{t("appointments.actions.cancel")}
</Button>
</Form>
}
>
<Button <Button
// onClick={() => handleCancel(event.id)} // onClick={() => handleCancel(event.id)}
disabled={event.arrived} disabled={event.arrived}
> >
{t("appointments.actions.cancel")} {t("appointments.actions.cancel")}
</Button> </Button>
</Popover> ) : (
<Popover
trigger="click"
disabled={event.arrived}
content={
<Form
layout="vertical"
onFinish={({ lost_sale_reason }) => {
handleCancel({ id: event.id, lost_sale_reason });
}}
>
<Form.Item
name="lost_sale_reason"
label={t("jobs.fields.lost_sale_reason")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
label: lsr,
value: lsr,
}))}
/>
</Form.Item>
<Button htmlType="submit">
{t("appointments.actions.cancel")}
</Button>
</Form>
}
>
<Button
// onClick={() => handleCancel(event.id)}
disabled={event.arrived}
>
{t("appointments.actions.cancel")}
</Button>
</Popover>
)}
{event.isintake ? ( {event.isintake ? (
<Button <Button
disabled={event.arrived} disabled={event.arrived}

View File

@@ -8,7 +8,18 @@ import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import AlertComponent from "../alert/alert.component"; 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 { t } = useTranslation();
const { loading, error, data } = useQuery(GET_JOB_LINE_ORDERS, { const { loading, error, data } = useQuery(GET_JOB_LINE_ORDERS, {
fetchPolicy: "network-only", fetchPolicy: "network-only",
@@ -23,7 +34,7 @@ export default function JobLinesExpander({ jobline, jobid }) {
return ( return (
<Row> <Row>
<Col md={24} lg={12}> <Col md={24} lg={8}>
<Typography.Title level={4}> <Typography.Title level={4}>
{t("parts_orders.labels.parts_orders")} {t("parts_orders.labels.parts_orders")}
</Typography.Title> </Typography.Title>
@@ -49,7 +60,7 @@ export default function JobLinesExpander({ jobline, jobid }) {
)} )}
</Timeline> </Timeline>
</Col> </Col>
<Col md={24} lg={12}> <Col md={24} lg={8}>
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title> <Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
<Timeline> <Timeline>
{data.billlines.length > 0 ? ( {data.billlines.length > 0 ? (
@@ -71,7 +82,7 @@ export default function JobLinesExpander({ jobline, jobid }) {
</Col> </Col>
<Col span={4}> <Col span={4}>
<span> <span>
{`${t("billlines.fields.actual_cost")}: `} {`${t("billlines.fields.actual_cost")}: `}
<CurrencyFormatter>{line.actual_cost}</CurrencyFormatter> <CurrencyFormatter>{line.actual_cost}</CurrencyFormatter>
</span> </span>
</Col> </Col>
@@ -89,6 +100,37 @@ export default function JobLinesExpander({ jobline, jobid }) {
)} )}
</Timeline> </Timeline>
</Col> </Col>
<Col md={24} lg={8}>
<Typography.Title level={4}>
{t("parts_dispatch.labels.parts_dispatch")}
</Typography.Title>
<Timeline>
{data.parts_dispatch_lines.length > 0 ? (
data.parts_dispatch_lines.map((line) => (
<Timeline.Item key={line.id}>
<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>
</Timeline.Item>
))
) : (
<Timeline.Item>
{t("parts_orders.labels.notyetordered")}
</Timeline.Item>
)}
</Timeline>
</Col>
</Row> </Row>
); );
} }

View File

@@ -86,6 +86,10 @@ export function JobLinesComponent({
bodyshop.imexshopid bodyshop.imexshopid
); );
const [selectedLines, setSelectedLines] = useState([]); const [selectedLines, setSelectedLines] = useState([]);
console.log(
"🚀 ~ file: job-lines.component.jsx:89 ~ selectedLines:",
selectedLines
);
const [state, setState] = useState({ const [state, setState] = useState({
sortedInfo: {}, sortedInfo: {},
filteredInfo: {}, filteredInfo: {},
@@ -114,9 +118,7 @@ export function JobLinesComponent({
onCell: (record) => ({ onCell: (record) => ({
className: record.manual_line && "job-line-manual", className: record.manual_line && "job-line-manual",
style: { style: {
...(record.critical || true ...(record.critical ? { boxShadow: " -.5em 0 0 #FFC107" } : {}),
? { boxShadow: " -.5em 0 0 #FFC107" }
: {}),
}, },
}), }),
sortOrder: sortOrder:
@@ -134,7 +136,7 @@ export function JobLinesComponent({
onCell: (record) => ({ onCell: (record) => ({
className: record.manual_line && "job-line-manual", className: record.manual_line && "job-line-manual",
style: { style: {
...(record.parts_dispatch_lines[0]?.accepted_at || true ...(record.parts_dispatch_lines[0]?.accepted_at
? { boxShadow: " -.5em 0 0 #FFC107" } ? { boxShadow: " -.5em 0 0 #FFC107" }
: {}), : {}),
}, },
@@ -426,7 +428,7 @@ export function JobLinesComponent({
const markedTypes = [e.key]; const markedTypes = [e.key];
if (e.key === "PAN") markedTypes.push("PAP"); if (e.key === "PAN") markedTypes.push("PAP");
if (e.key === "PAS") markedTypes.push("PASL"); if (e.key === "PAS") markedTypes.push("PASL");
setSelectedLines( setSelectedLines((selectedLines) =>
_.uniq([ _.uniq([
...selectedLines, ...selectedLines,
...jobLines.filter( ...jobLines.filter(
@@ -664,8 +666,17 @@ export function JobLinesComponent({
onSelectAll: (selected, selectedRows, changeRows) => { onSelectAll: (selected, selectedRows, changeRows) => {
setSelectedLines(selectedRows); setSelectedLines(selectedRows);
}, },
onSelect: (record, selected, selectedRows, nativeEvent) => onSelect: (record, selected, selectedRows, nativeEvent) => {
setSelectedLines(selectedRows), if (selected) {
setSelectedLines((selectedLines) =>
_.uniqBy([...selectedLines, record], "id")
);
} else {
setSelectedLines((selectedLines) =>
selectedLines.filter((l) => l.id !== record.id)
);
}
},
}} }}
/> />
</div> </div>

View File

@@ -70,7 +70,7 @@ export function JobLineDispatchButton({
notification.open({ notification.open({
type: "error", type: "error",
message: t("parts_dispatch.errors.creating", { message: t("parts_dispatch.errors.creating", {
error: JSON.stringify(result.errors), error: result.errors,
}), }),
}); });
} else { } else {
@@ -79,7 +79,7 @@ export function JobLineDispatchButton({
{ {
name: Templates.parts_dispatch.key, name: Templates.parts_dispatch.key,
variables: { variables: {
id: result.data.insert_part_dispatch_one.id, id: result.data.insert_parts_dispatch_one.id,
}, },
}, },
{}, {},
@@ -91,7 +91,7 @@ export function JobLineDispatchButton({
notification.open({ notification.open({
type: "error", type: "error",
message: t("parts_dispatch.errors.creating", { message: t("parts_dispatch.errors.creating", {
error: JSON.stringify(error), error: error,
}), }),
}); });
} finally { } finally {
@@ -137,7 +137,12 @@ export function JobLineDispatchButton({
</Form.Item> </Form.Item>
<Space wrap> <Space wrap>
<Button type="danger" onClick={() => form.submit()} loading={loading}> <Button
type="danger"
onClick={() => form.submit()}
loading={loading}
disabled={selectedLines.length === 0}
>
{t("general.actions.save")} {t("general.actions.save")}
</Button> </Button>
<Button onClick={() => setVisible(false)}> <Button onClick={() => setVisible(false)}>

View File

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

View File

@@ -1,26 +1,26 @@
import { Button, Card, Space, Table } from "antd";
import { EditFilled } from "@ant-design/icons"; import { EditFilled } from "@ant-design/icons";
import { Button, Card, Space, Table } from "antd";
import Dinero from "dinero.js"; import Dinero from "dinero.js";
import React, { useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import {
openChatByPhone,
setMessage,
} from "../../redux/messaging/messaging.actions";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort, dateSort } from "../../utils/sorters";
import DataLabel from "../data-label/data-label.component"; import DataLabel from "../data-label/data-label.component";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
import PaymentExpandedRowComponent from "../payment-expanded-row/payment-expanded-row.component"; import PaymentExpandedRowComponent from "../payment-expanded-row/payment-expanded-row.component";
import { import PaymentsGenerateLink from "../payments-generate-link/payments-generate-link.component";
setMessage, import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
openChatByPhone, import { useTreatments } from "@splitsoftware/splitio-react";
} from "../../redux/messaging/messaging.actions";
import { parsePhoneNumber } from "libphonenumber-js";
import axios from "axios";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -46,12 +46,17 @@ export function JobPayments({
setCardPaymentContext, setCardPaymentContext,
refetch, refetch,
}) { }) {
const { ImEXPay } = useTreatments(
["ImEXPay"],
{},
bodyshop && bodyshop.imexshopid
);
const { t } = useTranslation(); const { t } = useTranslation();
const [state, setState] = useState({ const [state, setState] = useState({
sortedInfo: {}, sortedInfo: {},
filteredInfo: {}, filteredInfo: {},
}); });
const [generatingURL, setGeneratingtURL] = useState(false);
const columns = [ const columns = [
{ {
@@ -165,39 +170,21 @@ export function JobPayments({
title={t("payments.labels.title")} title={t("payments.labels.title")}
extra={ extra={
<Space wrap> <Space wrap>
<Button {ImEXPay.treatment === "on" && (
disabled={!job.converted} <>
loading={generatingURL} <Button
onClick={async () => { onClick={() =>
const p = parsePhoneNumber(job.ownr_ph1, "CA"); setCardPaymentContext({
setGeneratingtURL(true); actions: { refetch },
const response = await axios.post( context: { jobid: job.id, balance },
"/intellipay/generate_payment_url", })
{
bodyshop,
amount: balance.getAmount(),
account: job.ro_number,
} }
); >
setGeneratingtURL(false); {t("menus.header.entercardpayment")}
</Button>
console.log("SMS", response); <PaymentsGenerateLink job={job} />
</>
openChatByPhone({ )}
phone_num: p.formatInternational(),
jobid: job.id,
});
setMessage(
t("appointments.labels.smspaymentreminder", {
shopname: bodyshop.shopname,
amount: balance.toFormat(),
payment_link: response.data.shorUrl,
})
);
}}
>
{t("menus.header.paymentremindersms")}
</Button>
<Button <Button
disabled={!job.converted} disabled={!job.converted}
onClick={() => onClick={() =>
@@ -209,16 +196,7 @@ export function JobPayments({
> >
{t("menus.header.enterpayment")} {t("menus.header.enterpayment")}
</Button> </Button>
<Button
onClick={() =>
setCardPaymentContext({
actions: { refetch },
context: { jobid: job.id, balance },
})
}
>
{t("menus.header.entercardpayment")}
</Button>
<DataLabel <DataLabel
valueStyle={{ color: balance.getAmount() !== 0 ? "red" : "green" }} valueStyle={{ color: balance.getAmount() !== 0 ? "red" : "green" }}
label={t("payments.labels.balance")} label={t("payments.labels.balance")}

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

@@ -123,10 +123,10 @@ export default function JobTotalsTableLabor({ job }) {
<Space> <Space>
{t("jobs.labels.mapa")} {t("jobs.labels.mapa")}
{job.materials && {job.materials &&
job.materials.mapa && job.materials.MAPA &&
job.materials.mapa.cal_maxdlr !== undefined && job.materials.MAPA.cal_maxdlr !== undefined &&
t("jobs.labels.threshhold", { t("jobs.labels.threshhold", {
amount: job.materials.mapa.cal_maxdlr, amount: job.materials.MAPA.cal_maxdlr,
})} })}
</Space> </Space>
</Table.Summary.Cell> </Table.Summary.Cell>
@@ -147,10 +147,10 @@ export default function JobTotalsTableLabor({ job }) {
<Space wrap> <Space wrap>
{t("jobs.labels.mash")} {t("jobs.labels.mash")}
{job.materials && {job.materials &&
job.materials.mash && job.materials.MASH &&
job.materials.mash.cal_maxdlr !== undefined && job.materials.MASH.cal_maxdlr !== undefined &&
t("jobs.labels.threshhold", { t("jobs.labels.threshhold", {
amount: job.materials.mash.cal_maxdlr, amount: job.materials.MASH.cal_maxdlr,
})} })}
</Space> </Space>
</Table.Summary.Cell> </Table.Summary.Cell>

View File

@@ -28,26 +28,46 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
total: job.job_totals.totals.subtotal, total: job.job_totals.totals.subtotal,
bold: true, bold: true,
}, },
{
key: t("jobs.labels.local_tax_amt"), ...(job.job_totals.totals.us_sales_tax_breakdown
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"
? [ ? [
{ {
key: t("jobs.fields.ca_bc_pvrt"), key:
total: job.job_totals.additional.pvrt, bodyshop.md_responsibility_centers.taxes.tax_ty1?.tax_type1 ||
"T1",
total: job.job_totals.totals.us_sales_tax_breakdown.ty1Tax,
},
{
key:
bodyshop.md_responsibility_centers.taxes.tax_ty2?.tax_type2 ||
"T2",
total: job.job_totals.totals.us_sales_tax_breakdown.ty2Tax,
},
{
key:
bodyshop.md_responsibility_centers.taxes.tax_ty3?.tax_type3 ||
"T3",
total: job.job_totals.totals.us_sales_tax_breakdown.ty3Tax,
},
{
key:
bodyshop.md_responsibility_centers.taxes.tax_ty4?.tax_type4 ||
"T4",
total: job.job_totals.totals.us_sales_tax_breakdown.ty4Tax,
},
{
key:
bodyshop.md_responsibility_centers.taxes.tax_ty5?.tax_type5 ||
"T5",
total: job.job_totals.totals.us_sales_tax_breakdown.ty5Tax,
}, },
] ]
: []), : [
{ {
key: t("jobs.labels.federal_tax_amt"), key: t("jobs.labels.state_tax_amt"),
total: job.job_totals.totals.federal_tax, total: job.job_totals.totals.state_tax,
}, },
]),
{ {
key: t("jobs.labels.total_repairs"), key: t("jobs.labels.total_repairs"),
total: job.job_totals.totals.total_repairs, total: job.job_totals.totals.total_repairs,
@@ -57,10 +77,10 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
key: t("jobs.fields.ded_amt"), key: t("jobs.fields.ded_amt"),
total: job.job_totals.totals.custPayable.deductible, total: job.job_totals.totals.custPayable.deductible,
}, },
{ // {
key: t("jobs.fields.federal_tax_payable"), // key: t("jobs.fields.federal_tax_payable"),
total: job.job_totals.totals.custPayable.federal_tax, // total: job.job_totals.totals.custPayable.federal_tax,
}, // },
{ {
key: t("jobs.fields.other_amount_payable"), key: t("jobs.fields.other_amount_payable"),
total: job.job_totals.totals.custPayable.other_customer_amount, total: job.job_totals.totals.custPayable.other_customer_amount,
@@ -81,7 +101,7 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
bold: true, bold: true,
}, },
]; ];
}, [job.job_totals, t, bodyshop.region_config]); }, [job.job_totals, t, bodyshop.md_responsibility_centers]);
const columns = [ const columns = [
{ {

View File

@@ -6,8 +6,9 @@ import {
useQuery, useQuery,
} from "@apollo/client"; } from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react"; import { useTreatments } from "@splitsoftware/splitio-react";
import { Col, notification, Row } from "antd"; import { Button, Col, Row, notification } from "antd";
import Axios from "axios"; import Axios from "axios";
import _ from "lodash";
import moment from "moment"; import moment from "moment";
import queryString from "query-string"; import queryString from "query-string";
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
@@ -29,7 +30,6 @@ import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import confirmDialog from "../../utils/asyncConfirm";
import AuditTrailMapping from "../../utils/AuditTrailMappings"; import AuditTrailMapping from "../../utils/AuditTrailMappings";
import CriticalPartsScan from "../../utils/criticalPartsScan"; import CriticalPartsScan from "../../utils/criticalPartsScan";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
@@ -40,7 +40,6 @@ import OwnerFindModalContainer from "../owner-find-modal/owner-find-modal.contai
import { GetSupplementDelta } from "./jobs-available-supplement.estlines.util"; import { GetSupplementDelta } from "./jobs-available-supplement.estlines.util";
import HeaderFields from "./jobs-available-supplement.headerfields"; import HeaderFields from "./jobs-available-supplement.headerfields";
import JobsAvailableTableComponent from "./jobs-available-table.component"; import JobsAvailableTableComponent from "./jobs-available-table.component";
import _ from "lodash";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -90,13 +89,14 @@ export function JobsAvailableContainer({
const modalSearchState = useState(""); const modalSearchState = useState("");
//Import Scenario //Import Scenario
const onOwnerFindModalOk = async () => { const onOwnerFindModalOk = async (lazyData) => {
logImEXEvent("job_import_new"); logImEXEvent("job_import_new");
setOwnerModalVisible(false); setOwnerModalVisible(false);
setInsertLoading(true); setInsertLoading(true);
const estData = replaceEmpty(
const estData = replaceEmpty(estDataRaw.data.available_jobs_by_pk); lazyData?.available_jobs_by_pk || estDataRaw.data.available_jobs_by_pk
);
if (!(estData && estData.est_data)) { if (!(estData && estData.est_data)) {
//We don't have the right data. Error! //We don't have the right data. Error!
@@ -139,6 +139,7 @@ export function JobsAvailableContainer({
// owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"), // owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"),
// job_totals: newTotals, // job_totals: newTotals,
date_open: moment(), date_open: moment(),
status: bodyshop.md_ro_statuses.default_imported,
notes: { notes: {
data: { data: {
created_by: currentUser.email, created_by: currentUser.email,
@@ -160,47 +161,50 @@ export function JobsAvailableContainer({
delete newJob.vehicle; delete newJob.vehicle;
} }
insertNewJob({ if (typeof newJob.kmin === "string") {
variables: { newJob.kmin = null;
job: newJob, }
},
})
.then((r) => {
Axios.post("/job/totalsssu", {
id: r.data.insert_jobs.returning[0].id,
});
if (CriticalPartsScanning.treatment === "on") { try {
CriticalPartsScan(r.data.insert_jobs.returning[0].id); const r = await insertNewJob({
} variables: {
notification["success"]({ job: newJob,
message: t("jobs.successes.created"), },
onClick: () => { });
history.push(`/manage/jobs/${r.data.insert_jobs.returning[0].id}`); await Axios.post("/job/totalsssu", {
}, id: r.data.insert_jobs.returning[0].id,
}); });
//Job has been inserted. Clean up the available jobs record.
insertAuditTrail({ if (CriticalPartsScanning.treatment === "on") {
jobid: r.data.insert_jobs.returning[0].id, CriticalPartsScan(r.data.insert_jobs.returning[0].id);
operation: AuditTrailMapping.jobimported(), }
}); notification["success"]({
message: t("jobs.successes.created"),
onClick: () => {
history.push(`/manage/jobs/${r.data.insert_jobs.returning[0].id}`);
},
});
//Job has been inserted. Clean up the available jobs record.
deleteJob({ insertAuditTrail({
variables: { id: estData.id }, jobid: r.data.insert_jobs.returning[0].id,
}).then((r) => { operation: AuditTrailMapping.jobimported(),
refetch(); });
setInsertLoading(false);
}); await deleteJob({
}) variables: { id: estData.id },
.catch((r) => { }).then((r) => {
//error while inserting
notification["error"]({
message: t("jobs.errors.creating", { error: r.message }),
});
refetch(); refetch();
setInsertLoading(false); setInsertLoading(false);
}); });
} catch (r) {
//error while inserting
notification["error"]({
message: t("jobs.errors.creating", { error: r.message }),
});
refetch();
setInsertLoading(false);
}
}; };
//Suplement scenario //Suplement scenario
@@ -387,6 +391,25 @@ export function JobsAvailableContainer({
onCancel={onJobModalCancel} onCancel={onJobModalCancel}
modalSearchState={modalSearchState} modalSearchState={modalSearchState}
/> />
{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]}> <Row gutter={[16, 16]}>
<Col span={24}> <Col span={24}>
<JobsAvailableTableComponent <JobsAvailableTableComponent
@@ -418,98 +441,104 @@ function replaceEmpty(someObj, replaceValue = null) {
} }
async function CheckTaxRates(estData, bodyshop) { async function CheckTaxRates(estData, bodyshop) {
//LKQ Check // //LKQ Check
if ( // if (
!estData.parts_tax_rates?.PAL || // !estData.parts_tax_rates?.PAL ||
estData.parts_tax_rates?.PAL?.prt_tax_rt === null || // estData.parts_tax_rates?.PAL?.prt_tax_rt === null ||
estData.parts_tax_rates?.PAL?.prt_tax_rt === 0 // estData.parts_tax_rates?.PAL?.prt_tax_rt === 0
) { // ) {
const res = await confirmDialog( // const res = await confirmDialog(
`Rome 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.` // `Rome 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 (res) {
if (!estData.parts_tax_rates.PAL) { // if (!estData.parts_tax_rates.PAL) {
estData.parts_tax_rates.PAL = { // estData.parts_tax_rates.PAL = {
prt_discp: 0, // prt_discp: 0,
prt_mktyp: true, // prt_mktyp: true,
prt_mkupp: 0, // prt_mkupp: 0,
prt_type: "PAL", // prt_type: "PAL",
}; // };
} // }
estData.parts_tax_rates.PAL.prt_tax_rt = // estData.parts_tax_rates.PAL.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100; // bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.parts_tax_rates.PAL.prt_tax_in = true; // estData.parts_tax_rates.PAL.prt_tax_in = true;
} // }
} // }
//PAC Check // //PAC Check
if ( // if (
!estData.parts_tax_rates?.PAC || // !estData.parts_tax_rates?.PAC ||
estData.parts_tax_rates?.PAC?.prt_tax_rt === null || // estData.parts_tax_rates?.PAC?.prt_tax_rt === null ||
estData.parts_tax_rates?.PAC?.prt_tax_rt === 0 // estData.parts_tax_rates?.PAC?.prt_tax_rt === 0
) { // ) {
const res = await confirmDialog( // const res = await confirmDialog(
`Rome 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.` // `Rome 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 (res) {
if (!estData.parts_tax_rates.PAC) { // if (!estData.parts_tax_rates.PAC) {
estData.parts_tax_rates.PAC = { // estData.parts_tax_rates.PAC = {
prt_discp: 0, // prt_discp: 0,
prt_mktyp: true, // prt_mktyp: true,
prt_mkupp: 0, // prt_mkupp: 0,
prt_type: "PAC", // prt_type: "PAC",
}; // };
} // }
estData.parts_tax_rates.PAC.prt_tax_rt = // estData.parts_tax_rates.PAC.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100; // bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.parts_tax_rates.PAC.prt_tax_in = true; // estData.parts_tax_rates.PAC.prt_tax_in = true;
} // }
} // }
//PAM Check //PAM Check
if ( if (!estData.parts_tax_rates?.PAM) {
!estData.parts_tax_rates?.PAM || estData.parts_tax_rates.PAM = estData.parts_tax_rates.PAC;
estData.parts_tax_rates?.PAM?.prt_tax_rt === null ||
estData.parts_tax_rates?.PAM?.prt_tax_rt === 0
) {
const res = await confirmDialog(
`Rome 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;
}
} }
if ( // //PAM Check
!estData.parts_tax_rates?.PAR || // if (
estData.parts_tax_rates?.PAR?.prt_tax_rt === null || // !estData.parts_tax_rates?.PAM ||
estData.parts_tax_rates?.PAR?.prt_tax_rt === 0 // estData.parts_tax_rates?.PAM?.prt_tax_rt === null ||
) { // estData.parts_tax_rates?.PAM?.prt_tax_rt === 0
const res = await confirmDialog( // ) {
`Rome 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(
); // `Rome 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.PAR) { // if (res) {
estData.parts_tax_rates.PAR = { // if (!estData.parts_tax_rates.PAM) {
prt_discp: 0, // estData.parts_tax_rates.PAM = {
prt_mktyp: true, // prt_discp: 0,
prt_mkupp: 0, // prt_mktyp: true,
prt_type: "PAR", // prt_mkupp: 0,
}; // prt_type: "PAM",
} // };
estData.parts_tax_rates.PAR.prt_tax_rt = // }
bodyshop.bill_tax_rates.state_tax_rate / 100; // estData.parts_tax_rates.PAM.prt_tax_rt =
estData.parts_tax_rates.PAR.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
// ) {
// const res = await confirmDialog(
// `Rome 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",
// };
// }
// 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;
// }
// }
//IO-1387 If a sublet line is NOT R&R, use the labor tax. If it is, use the sublet tax rate. //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. //Currently limited to SK shops only.
@@ -537,8 +566,7 @@ async function ResolveCCCLineIssues(estData, bodyshop) {
//This needs to be done before cleansing unq_seq since some misc prices could move over. //This needs to be done before cleansing unq_seq since some misc prices could move over.
estData.joblines.data.forEach((line) => { estData.joblines.data.forEach((line) => {
if (line.misc_amt && line.misc_amt !== 0) { if (line.misc_amt && line.misc_amt !== 0) {
line.act_price = line.misc_amt; line.act_price = line.act_price + line.misc_amt;
line.part_type = "PAS";
line.tax_part = !!line.misc_tax; line.tax_part = !!line.misc_tax;
} }
}); });

View File

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

View File

@@ -4,22 +4,35 @@ import axios from "axios";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { useHistory } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { auth } from "../../firebase/firebase.utils"; import { auth, logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import { logImEXEvent } from "../../firebase/firebase.utils"; import client from "../../utils/GraphQLClient";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { useHistory } from "react-router-dom";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
}); });
function updateJobCache(items) {
client.cache.modify({
id: "ROOT_QUERY",
fields: {
jobs(existingJobs = []) {
return existingJobs.filter(
(jobRef) => jobRef.__ref.includes(items) === false
);
},
},
});
}
export function JobsCloseExportButton({ export function JobsCloseExportButton({
bodyshop, bodyshop,
currentUser, currentUser,
@@ -101,6 +114,9 @@ export function JobsCloseExportButton({
//Check to see if any of them failed. If they didn't don't execute the update. //Check to see if any of them failed. If they didn't don't execute the update.
const failedTransactions = PartnerResponse.data.filter((r) => !r.success); const failedTransactions = PartnerResponse.data.filter((r) => !r.success);
const successfulTransactions = PartnerResponse.data.filter(
(r) => r.success
);
if (failedTransactions.length > 0) { if (failedTransactions.length > 0) {
//Uh oh. At least one was no good. //Uh oh. At least one was no good.
failedTransactions.forEach((ft) => { failedTransactions.forEach((ft) => {
@@ -159,12 +175,15 @@ export function JobsCloseExportButton({
}, },
}); });
if (!jobUpdateResponse.errors) { if (!!!jobUpdateResponse.errors) {
notification.open({ notification.open({
type: "success", type: "success",
key: "jobsuccessexport", key: "jobsuccessexport",
message: t("jobs.successes.exported"), message: t("jobs.successes.exported"),
}); });
updateJobCache(
jobUpdateResponse.data.update_jobs.returning.map((job) => job.id)
);
} else { } else {
notification["error"]({ notification["error"]({
message: t("jobs.errors.exporting", { message: t("jobs.errors.exporting", {
@@ -173,13 +192,31 @@ export function JobsCloseExportButton({
}); });
} }
} }
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) {
notification.open({
type: "success",
key: "jobsuccessexport",
message: t("jobs.successes.exported"),
});
updateJobCache([
...new Set(
successfulTransactions.map(
(st) =>
st[
bodyshop.accountingconfig && bodyshop.accountingconfig.qbo
? "jobid"
: "id"
]
)
),
]);
}
if (setSelectedJobs) { if (setSelectedJobs) {
setSelectedJobs((selectedJobs) => { setSelectedJobs((selectedJobs) => {
return selectedJobs.filter((i) => i !== jobId); return selectedJobs.filter((i) => i !== jobId);
}); });
} }
} }
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
setLoading(false); setLoading(false);
}; };

View File

@@ -1,4 +1,4 @@
import { Collapse, Form, Input, InputNumber, Select, Switch } from "antd"; import { Collapse, Form, Input, Select, Switch } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -12,6 +12,12 @@ import FormItemPhone, {
} from "../form-items-formatted/phone-form-item.component"; } from "../form-items-formatted/phone-form-item.component";
import JobsDetailRatesChangeButton from "../jobs-detail-rates-change-button/jobs-detail-rates-change-button.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 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 JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -258,26 +264,28 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
<CurrencyInput /> <CurrencyInput />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow> {
<Form.Item // <LayoutFormRow>
label={t("jobs.fields.federal_tax_rate")} // <Form.Item
name="federal_tax_rate" // label={t("jobs.fields.federal_tax_rate")}
> // name="federal_tax_rate"
<InputNumber min={0} max={1} precision={2} /> // >
</Form.Item> // <InputNumber min={0} max={1} precision={2} />
<Form.Item // </Form.Item>
label={t("jobs.fields.state_tax_rate")} // <Form.Item
name="state_tax_rate" // label={t("jobs.fields.state_tax_rate")}
> // name="state_tax_rate"
<InputNumber min={0} max={1} precision={2} /> // >
</Form.Item> // <InputNumber min={0} max={1} precision={2} />
<Form.Item // </Form.Item>
label={t("jobs.fields.local_tax_rate")} // <Form.Item
name="local_tax_rate" // label={t("jobs.fields.local_tax_rate")}
> // name="local_tax_rate"
<InputNumber min={0} max={1} precision={2} /> // >
</Form.Item> // <InputNumber min={0} max={1} precision={2} />
</LayoutFormRow> // </Form.Item>
// </LayoutFormRow>
}
<LayoutFormRow> <LayoutFormRow>
<Form.Item label={t("jobs.fields.rate_lab")} name="rate_lab"> <Form.Item label={t("jobs.fields.rate_lab")} name="rate_lab">
<CurrencyInput /> <CurrencyInput />
@@ -356,6 +364,10 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
required={selected && true} required={selected && true}
form={form} form={form}
/> />
<JobsDetailRatesLabor form={form} />
<JobsDetailRatesMaterials form={form} />
<JobsDetailRatesOther form={form} />
<JobsDetailRatesTaxes form={form} />
</div> </div>
); );
} }

View File

@@ -143,63 +143,67 @@ export function JobsDetailHeaderActions({
<Menu.Item <Menu.Item
disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled} disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled}
> >
<Popover {job.status !== bodyshop.md_ro_statuses.default_scheduled ? (
trigger="click" t("menus.jobsactions.cancelallappointments")
disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled} ) : (
content={ <Popover
<Form trigger="click"
layout="vertical" disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled}
onFinish={async ({ lost_sale_reason }) => { content={
const jobUpdate = await cancelAllAppointments({ <Form
variables: { layout="vertical"
jobid: job.id, onFinish={async ({ lost_sale_reason }) => {
job: { const jobUpdate = await cancelAllAppointments({
date_scheduled: null, variables: {
scheduled_in: null, jobid: job.id,
scheduled_completion: null, job: {
lost_sale_reason, date_scheduled: null,
status: bodyshop.md_ro_statuses.default_imported, scheduled_in: null,
scheduled_completion: null,
lost_sale_reason,
status: bodyshop.md_ro_statuses.default_imported,
},
}, },
},
});
if (!jobUpdate.errors) {
notification["success"]({
message: t("appointments.successes.canceled"),
}); });
return; if (!jobUpdate.errors) {
} notification["success"]({
}} message: t("appointments.successes.canceled"),
> });
<Form.Item return;
name="lost_sale_reason" }
label={t("jobs.fields.lost_sale_reason")} }}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
> >
<Select <Form.Item
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({ name="lost_sale_reason"
label: lsr, label={t("jobs.fields.lost_sale_reason")}
value: lsr, rules={[
}))} {
/> required: true,
</Form.Item> //message: t("general.validation.required"),
<Button },
htmlType="submit" ]}
disabled={ >
job.status !== bodyshop.md_ro_statuses.default_scheduled <Select
} options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
> label: lsr,
{t("appointments.actions.cancel")} value: lsr,
</Button> }))}
</Form> />
} </Form.Item>
> <Button
{t("menus.jobsactions.cancelallappointments")} htmlType="submit"
</Popover> disabled={
job.status !== bodyshop.md_ro_statuses.default_scheduled
}
>
{t("appointments.actions.cancel")}
</Button>
</Form>
}
>
{t("menus.jobsactions.cancelallappointments")}
</Popover>
)}
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
disabled={ disabled={
@@ -245,7 +249,12 @@ export function JobsDetailHeaderActions({
setTimeTicketContext({ setTimeTicketContext({
actions: {}, actions: {},
context: { jobId: job.id }, context: {
jobId: job.id,
created_by: currentUser.displayName
? currentUser.email.concat(" | ", currentUser.displayName)
: currentUser.email,
},
}); });
}} }}
> >

View File

@@ -1,25 +1,21 @@
import { import { Divider, Form, Input, Select, Space, Switch, Tooltip } from "antd";
Divider,
Form,
Input,
InputNumber,
Select,
Space,
Switch,
Tooltip,
} from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CABCpvrtCalculator from "../ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component"; 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 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 JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
import FormRow from "../layout-form-row/layout-form-row.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 JobsDetailRatesParts from "./jobs-detail-rates.parts.component";
import JobsDetailRatesTaxes from "./jobs-detail-rates.taxes.component";
import JobsDetailRatesProfileOVerride from "./jobs-detail-rates.profile-override.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
@@ -84,14 +80,7 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
> >
<CurrencyInput disabled={jobRO || bodyshop.cdk_dealerid} /> <CurrencyInput disabled={jobRO || bodyshop.cdk_dealerid} />
</Form.Item> </Form.Item>
{bodyshop.region_config === "CA_BC" && (
<Space align="center">
<Form.Item label={t("jobs.fields.ca_bc_pvrt")} name="ca_bc_pvrt">
<CurrencyInput disabled={jobRO} min={0} />
</Form.Item>
<CABCpvrtCalculator form={form} disabled={jobRO} />
</Space>
)}
<Form.Item <Form.Item
label={t("jobs.fields.auto_add_ats")} label={t("jobs.fields.auto_add_ats")}
name="auto_add_ats" name="auto_add_ats"
@@ -120,41 +109,7 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
}} }}
</Form.Item> </Form.Item>
</FormRow> </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>
<Divider <Divider
orientation="left" orientation="left"
type="horizontal" type="horizontal"
@@ -242,7 +197,15 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
<CurrencyInput min={0} disabled={jobRO} /> <CurrencyInput min={0} disabled={jobRO} />
</Form.Item> </Form.Item>
</FormRow> </FormRow>
<Divider orientation="left">Tax Profile</Divider>
<JobsDetailRatesProfileOVerride form={form} />
<JobsDetailRatesParts form={form} /> <JobsDetailRatesParts form={form} />
<JobsDetailRatesLabor form={form} />
<JobsDetailRatesMaterials form={form} />
<JobsDetailRatesOther form={form} />
<JobsDetailRatesTaxes form={form} />
</div> </div>
); );
} }

View File

@@ -0,0 +1,427 @@
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,145 @@
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,104 @@
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

@@ -68,11 +68,51 @@ export function JobsDetailRatesParts({
}, },
]} ]}
> >
<InputNumber min={0} max={100} precision={4} disabled={jobRO} /> <InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
</Form.Item> </Form.Item>
); );
}} }}
</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>
<LayoutFormRow header={t("joblines.fields.part_types.PAC")}> <LayoutFormRow header={t("joblines.fields.part_types.PAC")}>
<Form.Item <Form.Item
@@ -118,11 +158,51 @@ export function JobsDetailRatesParts({
}, },
]} ]}
> >
<InputNumber min={0} max={100} precision={4} disabled={jobRO} /> <InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
</Form.Item> </Form.Item>
); );
}} }}
</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>
<LayoutFormRow header={t("joblines.fields.part_types.PAL")}> <LayoutFormRow header={t("joblines.fields.part_types.PAL")}>
<Form.Item <Form.Item
@@ -168,11 +248,51 @@ export function JobsDetailRatesParts({
}, },
]} ]}
> >
<InputNumber min={0} max={100} precision={4} disabled={jobRO} /> <InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
</Form.Item> </Form.Item>
); );
}} }}
</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>
<LayoutFormRow header={t("joblines.fields.part_types.PAG")}> <LayoutFormRow header={t("joblines.fields.part_types.PAG")}>
<Form.Item <Form.Item
@@ -218,11 +338,51 @@ export function JobsDetailRatesParts({
}, },
]} ]}
> >
<InputNumber min={0} max={100} precision={4} disabled={jobRO} /> <InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
</Form.Item> </Form.Item>
); );
}} }}
</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>
<LayoutFormRow header={t("joblines.fields.part_types.PAM")}> <LayoutFormRow header={t("joblines.fields.part_types.PAM")}>
<Form.Item <Form.Item
@@ -268,11 +428,51 @@ export function JobsDetailRatesParts({
}, },
]} ]}
> >
<InputNumber min={0} max={100} precision={4} disabled={jobRO} /> <InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
</Form.Item> </Form.Item>
); );
}} }}
</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>
<LayoutFormRow header={t("joblines.fields.part_types.PAN")}> <LayoutFormRow header={t("joblines.fields.part_types.PAN")}>
<Form.Item <Form.Item
@@ -318,11 +518,51 @@ export function JobsDetailRatesParts({
}, },
]} ]}
> >
<InputNumber min={0} max={100} precision={4} disabled={jobRO} /> <InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
</Form.Item> </Form.Item>
); );
}} }}
</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>
<LayoutFormRow header={t("joblines.fields.part_types.PAO")}> <LayoutFormRow header={t("joblines.fields.part_types.PAO")}>
<Form.Item <Form.Item
@@ -368,11 +608,51 @@ export function JobsDetailRatesParts({
}, },
]} ]}
> >
<InputNumber min={0} max={100} precision={4} disabled={jobRO} /> <InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
</Form.Item> </Form.Item>
); );
}} }}
</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>
<LayoutFormRow header={t("joblines.fields.part_types.PAP")}> <LayoutFormRow header={t("joblines.fields.part_types.PAP")}>
<Form.Item <Form.Item
@@ -418,11 +698,51 @@ export function JobsDetailRatesParts({
}, },
]} ]}
> >
<InputNumber min={0} max={100} precision={4} disabled={jobRO} /> <InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
</Form.Item> </Form.Item>
); );
}} }}
</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>
<LayoutFormRow header={t("joblines.fields.part_types.PAR")}> <LayoutFormRow header={t("joblines.fields.part_types.PAR")}>
<Form.Item <Form.Item
@@ -468,11 +788,51 @@ export function JobsDetailRatesParts({
}, },
]} ]}
> >
<InputNumber min={0} max={100} precision={4} disabled={jobRO} /> <InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
</Form.Item> </Form.Item>
); );
}} }}
</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>
<LayoutFormRow header={t("joblines.fields.part_types.PAS")}> <LayoutFormRow header={t("joblines.fields.part_types.PAS")}>
<Form.Item <Form.Item
@@ -518,11 +878,51 @@ export function JobsDetailRatesParts({
}, },
]} ]}
> >
<InputNumber min={0} max={100} precision={4} disabled={jobRO} /> <InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
</Form.Item> </Form.Item>
); );
}} }}
</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>
<LayoutFormRow header={t("joblines.fields.part_types.PASL")}> <LayoutFormRow header={t("joblines.fields.part_types.PASL")}>
<Form.Item <Form.Item
@@ -568,11 +968,51 @@ export function JobsDetailRatesParts({
}, },
]} ]}
> >
<InputNumber min={0} max={100} precision={4} disabled={jobRO} /> <InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
</Form.Item> </Form.Item>
); );
}} }}
</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>
<LayoutFormRow header={t("joblines.fields.part_types.CCDR")}> <LayoutFormRow header={t("joblines.fields.part_types.CCDR")}>
<Form.Item <Form.Item

View File

@@ -0,0 +1,42 @@
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,155 @@
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({ children: section, wrap: true }));
formItems.push(<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

@@ -13,12 +13,26 @@ import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import client from "../../utils/GraphQLClient";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
}); });
function updateJobCache(items) {
client.cache.modify({
id: "ROOT_QUERY",
fields: {
jobs(existingJobs = []) {
return existingJobs.filter(
(jobRef) => jobRef.__ref.includes(items) === false
);
},
},
});
}
export function JobsExportAllButton({ export function JobsExportAllButton({
bodyshop, bodyshop,
currentUser, currentUser,
@@ -96,7 +110,9 @@ export function JobsExportAllButton({
Object.keys(groupedData).map(async (key) => { Object.keys(groupedData).map(async (key) => {
//Check to see if any of them failed. If they didn't don't execute the update. //Check to see if any of them failed. If they didn't don't execute the update.
const failedTransactions = groupedData[key].filter((r) => !r.success); const failedTransactions = groupedData[key].filter((r) => !r.success);
const successfulTransactions = groupedData[key].filter(
(r) => r.success
);
if (failedTransactions.length > 0) { if (failedTransactions.length > 0) {
//Uh oh. At least one was no good. //Uh oh. At least one was no good.
failedTransactions.forEach((ft) => { failedTransactions.forEach((ft) => {
@@ -155,12 +171,17 @@ export function JobsExportAllButton({
}, },
}); });
if (!jobUpdateResponse.errors) { if (!!!jobUpdateResponse.errors) {
notification.open({ notification.open({
type: "success", type: "success",
key: "jobsuccessexport", key: "jobsuccessexport",
message: t("jobs.successes.exported"), message: t("jobs.successes.exported"),
}); });
updateJobCache(
jobUpdateResponse.data.update_jobs.returning.map(
(job) => job.id
)
);
} else { } else {
notification["error"]({ notification["error"]({
message: t("jobs.errors.exporting", { message: t("jobs.errors.exporting", {
@@ -169,14 +190,31 @@ export function JobsExportAllButton({
}); });
} }
} }
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) {
notification.open({
type: "success",
key: "jobsuccessexport",
message: t("jobs.successes.exported"),
});
updateJobCache([
...new Set(
successfulTransactions.map(
(st) =>
st[
bodyshop.accountingconfig && bodyshop.accountingconfig.qbo
? "jobid"
: "id"
]
)
),
]);
}
} }
}) })
); );
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
if (!!completedCallback) completedCallback([]); if (!!completedCallback) completedCallback([]);
if (!!loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);
setLoading(false); setLoading(false);
}; };

View File

@@ -6,6 +6,7 @@ import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort, statusSort } from "../../utils/sorters";
import OwnerDetailUpdateJobsComponent from "../owner-detail-update-jobs/owner-detail-update-jobs.component"; import OwnerDetailUpdateJobsComponent from "../owner-detail-update-jobs/owner-detail-update-jobs.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -15,6 +16,15 @@ const mapStateToProps = createStructuredSelector({
function OwnerDetailJobsComponent({ bodyshop, owner }) { function OwnerDetailJobsComponent({ bodyshop, owner }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [selectedJobs, setSelectedJobs] = useState([]); const [selectedJobs, setSelectedJobs] = useState([]);
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: { text: "" },
});
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const columns = [ const columns = [
{ {
title: t("jobs.fields.ro_number"), title: t("jobs.fields.ro_number"),
@@ -26,6 +36,9 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
{record.ro_number || t("general.labels.na")} {record.ro_number || t("general.labels.na")}
</Link> </Link>
), ),
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
}, },
{ {
title: t("jobs.fields.vehicle"), title: t("jobs.fields.vehicle"),
@@ -46,11 +59,17 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
title: t("jobs.fields.clm_no"), title: t("jobs.fields.clm_no"),
dataIndex: "clm_no", dataIndex: "clm_no",
key: "clm_no", key: "clm_no",
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder:
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
}, },
{ {
title: t("jobs.fields.status"), title: t("jobs.fields.status"),
dataIndex: "status", dataIndex: "status",
key: "status", key: "status",
sorter: (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses),
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
}, },
{ {
@@ -60,6 +79,9 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
render: (text, record) => ( render: (text, record) => (
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter> <CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
), ),
sorter: (a, b) => a.clm_total - b.clm_total,
sortOrder:
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
}, },
]; ];
@@ -80,6 +102,7 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
scroll={{ x: true }} scroll={{ x: true }}
rowKey="id" rowKey="id"
dataSource={owner.jobs} dataSource={owner.jobs}
onChange={handleTableChange}
rowSelection={{ rowSelection={{
onSelect: (record, selected, selectedRows) => { onSelect: (record, selected, selectedRows) => {
setSelectedJobs(selectedRows ? selectedRows.map((i) => i.id) : []); setSelectedJobs(selectedRows ? selectedRows.map((i) => i.id) : []);

View File

@@ -1,10 +1,35 @@
import { Card, Col, Row, Table } from "antd"; import { useMutation } from "@apollo/client";
import { Button, Card, Col, Row, Table, notification } from "antd";
import moment from "moment-business-days";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { UPDATE_PARTS_DISPATCH_LINE } from "../../graphql/parts-dispatch.queries";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
export default function PartsDispatchExpander({ dispatch, job }) { export default function PartsDispatchExpander({ dispatch, job }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [updateDispatchLine] = useMutation(UPDATE_PARTS_DISPATCH_LINE);
const handleAccept = async ({ partsDispatchLineId }) => {
const accepted_at = moment();
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 = [ const columns = [
{ {
@@ -28,9 +53,16 @@ export default function PartsDispatchExpander({ dispatch, job }) {
width: "20%", width: "20%",
//sorter: (a, b) => alphaSort(a.number, b.number), //sorter: (a, b) => alphaSort(a.number, b.number),
render: (text, record) => ( render: (text, record) =>
<DateTimeFormatter>{record.accepted_at}</DateTimeFormatter> record.accepted_at ? (
), <DateTimeFormatter>{record.accepted_at}</DateTimeFormatter>
) : (
<Button
onClick={() => handleAccept({ partsDispatchLineId: record.id })}
>
{t("parts_dispatch.actions.accept")}
</Button>
),
}, },
]; ];
return ( return (
@@ -38,7 +70,7 @@ export default function PartsDispatchExpander({ dispatch, job }) {
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col span={24}> <Col span={24}>
<Table <Table
pagination={false} rowKey={"id"}
dataSource={dispatch.parts_dispatch_lines} dataSource={dispatch.parts_dispatch_lines}
columns={columns} columns={columns}
/> />

View File

@@ -113,6 +113,8 @@ export function PartsOrderListTableComponent({
id: pol.id, id: pol.id,
line_desc: pol.line_desc, line_desc: pol.line_desc,
quantity: pol.quantity, quantity: pol.quantity,
act_price: pol.act_price,
oem_partno: pol.oem_partno,
}; };
}), }),
}, },

View File

@@ -79,6 +79,20 @@ export function PartsReceiveModalComponent({ bodyshop, form }) {
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item
label={t("joblines.fields.oem_partno")}
key={`${index}oem_partno`}
name={[field.name, "oem_partno"]}
>
<Input disabled />
</Form.Item>
<Form.Item
label={t("joblines.fields.act_price")}
key={`${index}act_price`}
name={[field.name, "act_price"]}
>
<Input disabled />
</Form.Item>
<Form.Item <Form.Item
label={t("joblines.fields.location")} label={t("joblines.fields.location")}
key={`${index}location`} key={`${index}location`}

View File

@@ -1,26 +1,39 @@
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, notification } from "antd"; import { Button, notification } from "antd";
import axios from "axios"; import axios from "axios";
import _ from "lodash";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { auth } from "../../firebase/firebase.utils"; import { auth, logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { UPDATE_BILLS } from "../../graphql/bills.queries"; import { UPDATE_BILLS } from "../../graphql/bills.queries";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import { logImEXEvent } from "../../firebase/firebase.utils"; import client from "../../utils/GraphQLClient";
import _ from "lodash";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { Link } from "react-router-dom";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
}); });
function updateBillCache(items) {
client.cache.modify({
id: "ROOT_QUERY",
fields: {
bills(existingJobs = []) {
return existingJobs.filter(
(billRef) => billRef.__ref.includes(items) === false
);
},
},
});
}
export function PayableExportAll({ export function PayableExportAll({
bodyshop, bodyshop,
currentUser, currentUser,
@@ -97,7 +110,9 @@ export function PayableExportAll({
proms.push( proms.push(
(async () => { (async () => {
const failedTransactions = groupedData[key].filter((r) => !r.success); const failedTransactions = groupedData[key].filter((r) => !r.success);
const successfulTransactions = groupedData[key].filter(
(r) => r.success
);
if (failedTransactions.length > 0) { if (failedTransactions.length > 0) {
//Uh oh. At least one was no good. //Uh oh. At least one was no good.
failedTransactions.map((ft) => failedTransactions.map((ft) =>
@@ -143,7 +158,15 @@ export function PayableExportAll({
const billUpdateResponse = await updateBill({ const billUpdateResponse = await updateBill({
variables: { variables: {
billIdList: [key], billIdList: successfulTransactions.map(
(st) =>
st[
bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo
? "billid"
: "id"
]
),
bill: { bill: {
exported: true, exported: true,
exported_at: new Date(), exported_at: new Date(),
@@ -156,6 +179,11 @@ export function PayableExportAll({
key: "billsuccessexport", key: "billsuccessexport",
message: t("bills.successes.exported"), message: t("bills.successes.exported"),
}); });
updateBillCache(
billUpdateResponse.data.update_bills.returning.map(
(bill) => bill.id
)
);
} else { } else {
notification["error"]({ notification["error"]({
message: t("bills.errors.exporting", { message: t("bills.errors.exporting", {
@@ -164,6 +192,26 @@ export function PayableExportAll({
}); });
} }
} }
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) {
notification.open({
type: "success",
key: "billsuccessexport",
message: t("bills.successes.exported"),
});
updateBillCache([
...new Set(
successfulTransactions.map(
(st) =>
st[
bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo
? "billid"
: "id"
]
)
),
]);
}
} }
})() })()
); );
@@ -172,8 +220,6 @@ export function PayableExportAll({
await Promise.all(proms); await Promise.all(proms);
if (!!completedCallback) completedCallback([]); if (!!completedCallback) completedCallback([]);
if (!!loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
setLoading(false); setLoading(false);
}; };

View File

@@ -4,22 +4,35 @@ import axios from "axios";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { auth } from "../../firebase/firebase.utils"; import { auth, logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { UPDATE_BILLS } from "../../graphql/bills.queries"; import { UPDATE_BILLS } from "../../graphql/bills.queries";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import { logImEXEvent } from "../../firebase/firebase.utils"; import client from "../../utils/GraphQLClient";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { Link } from "react-router-dom";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
}); });
function updateBillCache(items) {
client.cache.modify({
id: "ROOT_QUERY",
fields: {
bills(existingJobs = []) {
return existingJobs.filter(
(billRef) => billRef.__ref.includes(items) === false
);
},
},
});
}
export function PayableExportButton({ export function PayableExportButton({
bodyshop, bodyshop,
currentUser, currentUser,
@@ -159,6 +172,11 @@ export function PayableExportButton({
key: "billsuccessexport", key: "billsuccessexport",
message: t("bills.successes.exported"), message: t("bills.successes.exported"),
}); });
updateBillCache(
billUpdateResponse.data.update_bills.returning.map(
(bill) => bill.id
)
);
} else { } else {
notification["error"]({ notification["error"]({
message: t("bills.errors.exporting", { message: t("bills.errors.exporting", {
@@ -167,7 +185,25 @@ export function PayableExportButton({
}); });
} }
} }
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch(); if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) {
notification.open({
type: "success",
key: "billsuccessexport",
message: t("bills.successes.exported"),
});
updateBillCache([
...new Set(
successfulTransactions.map(
(st) =>
st[
bodyshop.accountingconfig && bodyshop.accountingconfig.qbo
? "billid"
: "id"
]
)
),
]);
}
if (setSelectedBills) { if (setSelectedBills) {
setSelectedBills((selectedBills) => { setSelectedBills((selectedBills) => {

View File

@@ -1,15 +1,24 @@
import React, { useState } from "react";
import { useMutation, useQuery } from "@apollo/client"; import { useMutation, useQuery } from "@apollo/client";
import {
Button,
Descriptions,
InputNumber,
Modal,
Space,
notification,
} from "antd";
import axios from "axios";
import moment from "moment";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { import {
GET_REFUNDABLE_AMOUNT_BY_JOBID, GET_REFUNDABLE_AMOUNT_BY_JOBID,
INSERT_PAYMENT_RESPONSE, INSERT_PAYMENT_RESPONSE,
QUERY_PAYMENT_RESPONSE_BY_PAYMENT_ID, QUERY_PAYMENT_RESPONSE_BY_PAYMENT_ID,
} from "../../graphql/payment_response.queries"; } from "../../graphql/payment_response.queries";
import { Button, Descriptions, InputNumber, Modal, notification } from "antd";
import moment from "moment";
import axios from "axios";
import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries"; import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries";
import { useTranslation } from "react-i18next"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateTimeFormatter } from "../../utils/DateFormatter";
const { confirm } = Modal; const { confirm } = Modal;
@@ -137,10 +146,10 @@ const PaymentExpandedRowComponent = ({ record, bodyshop }) => {
{payment_response?.response?.nameOnCard ?? ""} {payment_response?.response?.nameOnCard ?? ""}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.amount")}> <Descriptions.Item label={t("job_payments.titles.amount")}>
{record.amount} <CurrencyFormatter>{record.amount}</CurrencyFormatter>
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.dateOfPayment")}> <Descriptions.Item label={t("job_payments.titles.dateOfPayment")}>
{moment(record.created_at).format("YYYY-MM-DD HH:mm:ss")} {<DateTimeFormatter>{record.created_at}</DateTimeFormatter>}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.transactionid")}> <Descriptions.Item label={t("job_payments.titles.transactionid")}>
{record.transactionid} {record.transactionid}
@@ -151,17 +160,22 @@ const PaymentExpandedRowComponent = ({ record, bodyshop }) => {
<Descriptions.Item label={t("job_payments.titles.paymenttype")}> <Descriptions.Item label={t("job_payments.titles.paymenttype")}>
{record.type} {record.type}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.paymentnum")}>
{record.paymentnum}
</Descriptions.Item>
{payment_response && ( {payment_response && (
<Descriptions.Item label={t("job_payments.titles.refundamount")}> <Descriptions.Item label={t("job_payments.titles.refundamount")}>
<InputNumber <Space>
onChange={setRefundAmount} <InputNumber
max={max_refundable_amount} onChange={setRefundAmount}
min={0} max={max_refundable_amount}
/> min={0}
/>
<Button onClick={() => showConfirm(payment_response)}> <Button onClick={() => showConfirm(payment_response)}>
{t("job_payments.buttons.refundpayment")} {t("job_payments.buttons.refundpayment")}
</Button> </Button>
</Space>
</Descriptions.Item> </Descriptions.Item>
)} )}
</Descriptions> </Descriptions>

View File

@@ -13,12 +13,26 @@ import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import client from "../../utils/GraphQLClient";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
}); });
function updatePaymentCache(items) {
client.cache.modify({
id: "ROOT_QUERY",
fields: {
payments(existingJobs = []) {
return existingJobs.filter(
(paymentRef) => paymentRef.__ref.includes(items) === false
);
},
},
});
}
export function PaymentExportButton({ export function PaymentExportButton({
bodyshop, bodyshop,
currentUser, currentUser,
@@ -157,6 +171,11 @@ export function PaymentExportButton({
key: "paymentsuccessexport", key: "paymentsuccessexport",
message: t("payments.successes.exported"), message: t("payments.successes.exported"),
}); });
updatePaymentCache(
paymentUpdateResponse.data.update_payments.returning.map(
(payment) => payment.id
)
);
} else { } else {
notification["error"]({ notification["error"]({
message: t("payments.errors.exporting", { message: t("payments.errors.exporting", {
@@ -172,7 +191,25 @@ export function PaymentExportButton({
}); });
} }
} }
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch(); if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) {
notification.open({
type: "success",
key: "paymentsuccessexport",
message: t("payments.successes.exported"),
});
updatePaymentCache([
...new Set(
successfulTransactions.map(
(st) =>
st[
bodyshop.accountingconfig && bodyshop.accountingconfig.qbo
? "paymentid"
: "id"
]
)
),
]);
}
if (!!loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);
setLoading(false); setLoading(false);
}; };

View File

@@ -13,11 +13,25 @@ import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import client from "../../utils/GraphQLClient";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
}); });
function updatePaymentCache(items) {
client.cache.modify({
id: "ROOT_QUERY",
fields: {
payments(existingJobs = []) {
return existingJobs.filter(
(paymentRef) => paymentRef.__ref.includes(items) === false
);
},
},
});
}
export function PaymentsExportAllButton({ export function PaymentsExportAllButton({
bodyshop, bodyshop,
currentUser, currentUser,
@@ -25,7 +39,7 @@ export function PaymentsExportAllButton({
disabled, disabled,
loadingCallback, loadingCallback,
completedCallback, completedCallback,
refetch refetch,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [updatePayments] = useMutation(UPDATE_PAYMENTS); const [updatePayments] = useMutation(UPDATE_PAYMENTS);
@@ -84,7 +98,9 @@ export function PaymentsExportAllButton({
proms.push( proms.push(
(async () => { (async () => {
const failedTransactions = groupedData[key].filter((r) => !r.success); const failedTransactions = groupedData[key].filter((r) => !r.success);
const successfulTransactions = groupedData[key].filter(
(r) => r.success
);
if (failedTransactions.length > 0) { if (failedTransactions.length > 0) {
//Uh oh. At least one was no good. //Uh oh. At least one was no good.
failedTransactions.map((ft) => failedTransactions.map((ft) =>
@@ -130,7 +146,15 @@ export function PaymentsExportAllButton({
}); });
const paymentUpdateResponse = await updatePayments({ const paymentUpdateResponse = await updatePayments({
variables: { variables: {
paymentIdList: [key], paymentIdList: successfulTransactions.map(
(st) =>
st[
bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo
? "paymentid"
: "id"
]
),
payment: { payment: {
exportedat: new Date(), exportedat: new Date(),
}, },
@@ -142,6 +166,11 @@ export function PaymentsExportAllButton({
key: "paymentsuccessexport", key: "paymentsuccessexport",
message: t("payments.successes.exported"), message: t("payments.successes.exported"),
}); });
updatePaymentCache(
paymentUpdateResponse.data.update_payments.returning.map(
(payment) => payment.id
)
);
} else { } else {
notification["error"]({ notification["error"]({
message: t("payments.errors.exporting", { message: t("payments.errors.exporting", {
@@ -150,6 +179,26 @@ export function PaymentsExportAllButton({
}); });
} }
} }
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) {
notification.open({
type: "success",
key: "paymentsuccessexport",
message: t("payments.successes.exported"),
});
updatePaymentCache([
...new Set(
successfulTransactions.map(
(st) =>
st[
bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo
? "paymentid"
: "id"
]
)
),
]);
}
} }
})() })()
); );
@@ -157,7 +206,6 @@ export function PaymentsExportAllButton({
await Promise.all(proms); await Promise.all(proms);
if (!!completedCallback) completedCallback([]); if (!!completedCallback) completedCallback([]);
if (!!loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
setLoading(false); setLoading(false);
}; };

View File

@@ -0,0 +1,156 @@
import { CopyFilled } from "@ant-design/icons";
import { Button, Form, Popover, Space, message } from "antd";
import axios from "axios";
import Dinero from "dinero.js";
import { parsePhoneNumber } from "libphonenumber-js";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
openChatByPhone,
setMessage,
} from "../../redux/messaging/messaging.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
setMessage: (text) => dispatch(setMessage(text)),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(PaymentsGenerateLink);
export function PaymentsGenerateLink({
bodyshop,
callback,
job,
openChatByPhone,
setMessage,
}) {
const { t } = useTranslation();
const [form] = Form.useForm();
const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false);
const [paymentLink, setPaymentLink] = useState(null);
const handleFinish = async ({ amount }) => {
setLoading(true);
const p = parsePhoneNumber(job.ownr_ph1, "CA");
setLoading(true);
const response = await axios.post("/intellipay/generate_payment_url", {
bodyshop,
amount: amount,
account: job.ro_number,
invoice: job.id,
});
setLoading(false);
setPaymentLink(response.data.shorUrl);
openChatByPhone({
phone_num: p.formatInternational(),
jobid: job.id,
});
setMessage(
t("payments.labels.smspaymentreminder", {
shopname: bodyshop.shopname,
amount: amount,
payment_link: response.data.shorUrl,
})
);
//Add in confirmation & errors.
if (callback) callback();
// setVisible(false);
setLoading(false);
};
const popContent = (
<div>
<Form onFinish={handleFinish} layout="vertical" form={form}>
<Form.Item
label={t("payments.fields.amount")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name="amount"
>
<CurrencyFormItemComponent />
</Form.Item>
{paymentLink && (
<Space direction="vertical">
<Space
style={{ cursor: "pointer" }}
onClick={() => {
navigator.clipboard.writeText(paymentLink);
message.success(t("general.actions.copied"));
}}
>
<div
onClick={() => {
//Copy the link.
}}
>
{paymentLink}
</div>{" "}
<CopyFilled />
</Space>
<Button
onClick={() => {
const p = parsePhoneNumber(job.ownr_ph1, "CA");
openChatByPhone({
phone_num: p.formatInternational(),
jobid: job.id,
});
setMessage(
t("payments.labels.smspaymentreminder", {
shopname: bodyshop.shopname,
amount: Dinero({
amount: Math.round(form.getFieldValue("amount") * 100),
}).toFormat(),
payment_link: paymentLink,
})
);
}}
>
{t("general.actions.sendbysms")}
</Button>
</Space>
)}
</Form>
<Space>
<Button type="primary" onClick={() => form.submit()}>
{t("general.actions.submit")}
</Button>
<Button
onClick={() => {
form.resetFields();
setPaymentLink(null);
setVisible(false);
}}
>
{t("general.actions.cancel")}
</Button>
</Space>
</div>
);
return (
<Popover content={popContent} visible={visible}>
<Button onClick={() => setVisible(true)} loading={loading}>
{t("payments.actions.generatepaymentlink")}
</Button>
</Popover>
);
}

View File

@@ -5,11 +5,13 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { setEmailOptions } from "../../redux/email/email.actions"; import { setEmailOptions } from "../../redux/email/email.actions";
import { selectPrintCenter } from "../../redux/modals/modals.selectors"; import { selectPrintCenter } from "../../redux/modals/modals.selectors";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { GenerateDocument } from "../../utils/RenderTemplate"; import { GenerateDocument } from "../../utils/RenderTemplate";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
printCenterModal: selectPrintCenter, printCenterModal: selectPrintCenter,
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
technician: selectTechnician,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setEmailOptions: (e) => dispatch(setEmailOptions(e)), setEmailOptions: (e) => dispatch(setEmailOptions(e)),
@@ -22,6 +24,7 @@ export function PrintCenterItemComponent({
id, id,
bodyshop, bodyshop,
disabled, disabled,
technician,
}) { }) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { context } = printCenterModal; const { context } = printCenterModal;
@@ -44,19 +47,24 @@ export function PrintCenterItemComponent({
<Space wrap> <Space wrap>
{item.title} {item.title}
<PrinterOutlined onClick={renderToNewWindow} /> <PrinterOutlined onClick={renderToNewWindow} />
<MailOutlined {!technician ? (
onClick={() => { <MailOutlined
GenerateDocument( onClick={() => {
{ GenerateDocument(
name: item.key, {
variables: { id: id }, name: item.key,
}, variables: { id: id },
{ to: context.job && context.job.ownr_ea, subject: item.subject }, },
"e", {
id to: context.job && context.job.ownr_ea,
); subject: item.subject,
}} },
/> "e",
id
);
}}
/>
) : null}
{loading && <Spin />} {loading && <Spin />}
</Space> </Space>
</li> </li>

View File

@@ -1,31 +1,33 @@
import { PrinterFilled } from "@ant-design/icons";
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { Descriptions, Drawer, Space, PageHeader, Button } from "antd"; import { Button, Descriptions, Drawer, PageHeader, Space } from "antd";
import queryString from "query-string"; import queryString from "query-string";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom"; import { useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { QUERY_JOB_CARD_DETAILS } from "../../graphql/jobs.queries"; import { QUERY_JOB_CARD_DETAILS } from "../../graphql/jobs.queries";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import StartChatButton from "../chat-open-button/chat-open-button.component"; import StartChatButton from "../chat-open-button/chat-open-button.component";
import JobAtChange from "../job-at-change/job-at-change.component";
import JobDetailCardsDocumentsComponent from "../job-detail-cards/job-detail-cards.documents.component"; import JobDetailCardsDocumentsComponent from "../job-detail-cards/job-detail-cards.documents.component";
import JobDetailCardsNotesComponent from "../job-detail-cards/job-detail-cards.notes.component"; import JobDetailCardsNotesComponent from "../job-detail-cards/job-detail-cards.notes.component";
import JobDetailCardsPartsComponent from "../job-detail-cards/job-detail-cards.parts.component"; import JobDetailCardsPartsComponent from "../job-detail-cards/job-detail-cards.parts.component";
import JobEmployeeAssignments from "../job-employee-assignments/job-employee-assignments.container"; import JobEmployeeAssignments from "../job-employee-assignments/job-employee-assignments.container";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import ProductionRemoveButton from "../production-remove-button/production-remove-button.component";
import JobAtChange from "../job-at-change/job-at-change.component";
import { PrinterFilled } from "@ant-design/icons";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions";
import ScoreboardAddButton from "../job-scoreboard-add-button/job-scoreboard-add-button.component"; import ScoreboardAddButton from "../job-scoreboard-add-button/job-scoreboard-add-button.component";
import { selectBodyshop } from "../../redux/user/user.selectors"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import ProductionRemoveButton from "../production-remove-button/production-remove-button.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
technician: selectTechnician,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setPrintCenterContext: (context) => setPrintCenterContext: (context) =>
@@ -40,6 +42,7 @@ export function ProductionListDetail({
bodyshop, bodyshop,
jobs, jobs,
setPrintCenterContext, setPrintCenterContext,
technician,
}) { }) {
const search = queryString.parse(useLocation().search); const search = queryString.parse(useLocation().search);
const history = useHistory(); const history = useHistory();
@@ -66,7 +69,9 @@ export function ProductionListDetail({
title={theJob.ro_number} title={theJob.ro_number}
extra={ extra={
<Space wrap> <Space wrap>
<ProductionRemoveButton jobId={theJob.id} />{" "} {!technician ? (
<ProductionRemoveButton jobId={theJob.id} />
) : null}
<Button <Button
onClick={() => { onClick={() => {
setPrintCenterContext({ setPrintCenterContext({
@@ -82,7 +87,9 @@ export function ProductionListDetail({
<PrinterFilled /> <PrinterFilled />
{t("jobs.actions.printCenter")} {t("jobs.actions.printCenter")}
</Button> </Button>
<ScoreboardAddButton job={data ? data.jobs_by_pk : {}} /> {!technician ? (
<ScoreboardAddButton job={data ? data.jobs_by_pk : {}} />
) : null}
</Space> </Space>
} }
/> />

View File

@@ -59,11 +59,12 @@ export function ScheduleManualEvent({ bodyshop, event }) {
refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"], refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"],
}); });
} }
form.resetFields();
setVisibility(false);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally { } finally {
setLoading(false); setLoading(false);
setVisibility(false);
} }
}; };

View File

@@ -47,9 +47,7 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
bodyhrs: dayAcc.bodyhrs + dayVal.bodyhrs, bodyhrs: dayAcc.bodyhrs + dayVal.bodyhrs,
painthrs: dayAcc.painthrs + dayVal.painthrs, painthrs: dayAcc.painthrs + dayVal.painthrs,
sales: sales:
dayAcc.painthrs + dayAcc.sales + dayVal.job.job_totals.totals.subtotal.amount / 100,
dayVal.job.job_totals.totals.subtotal.amount / 100 +
2500,
}; };
}, },
{ bodyhrs: 0, painthrs: 0, sales: 0 } { bodyhrs: 0, painthrs: 0, sales: 0 }

View File

@@ -1,14 +1,14 @@
import React, { useEffect, useState } from "react"; import { useMutation, useQuery } from "@apollo/client";
import ShopInfoComponent from "./shop-info.component";
import { Form, notification } from "antd"; import { Form, notification } from "antd";
import { useQuery, useMutation } from "@apollo/client"; import moment from "moment";
import { QUERY_BODYSHOP, UPDATE_SHOP } from "../../graphql/bodyshop.queries"; import React, { useEffect, useState } from "react";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import AlertComponent from "../alert/alert.component";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { QUERY_BODYSHOP, UPDATE_SHOP } from "../../graphql/bodyshop.queries";
import AlertComponent from "../alert/alert.component";
import FormsFieldChanged from "../form-fields-changed-alert/form-fields-changed-alert.component"; import FormsFieldChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import moment from "moment"; import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import ShopInfoComponent from "./shop-info.component";
export default function ShopInfoContainer() { export default function ShopInfoContainer() {
const [form] = Form.useForm(); const [form] = Form.useForm();
const { t } = useTranslation(); const { t } = useTranslation();
@@ -52,13 +52,28 @@ export default function ShopInfoContainer() {
onFinish={handleFinish} onFinish={handleFinish}
initialValues={ initialValues={
data data
? { ? data.bodyshops[0].accountingconfig.ClosingPeriod
...data.bodyshops[0], ? {
schedule_start_time: moment( ...data.bodyshops[0],
data.bodyshops[0].schedule_start_time accountingconfig: {
), ...data.bodyshops[0].accountingconfig,
schedule_end_time: moment(data.bodyshops[0].schedule_end_time), ClosingPeriod: [
} moment(data.bodyshops[0].accountingconfig.ClosingPeriod[0]),
moment(data.bodyshops[0].accountingconfig.ClosingPeriod[1]),
],
},
schedule_start_time: moment(
data.bodyshops[0].schedule_start_time
),
schedule_end_time: moment(data.bodyshops[0].schedule_end_time),
}
: {
...data.bodyshops[0],
schedule_start_time: moment(
data.bodyshops[0].schedule_start_time
),
schedule_end_time: moment(data.bodyshops[0].schedule_end_time),
}
: null : null
} }
> >

View File

@@ -1,6 +1,8 @@
import { DeleteFilled } from "@ant-design/icons"; import { DeleteFilled } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react";
import { import {
Button, Button,
DatePicker,
Form, Form,
Input, Input,
InputNumber, InputNumber,
@@ -9,8 +11,13 @@ import {
Space, Space,
Switch, Switch,
} from "antd"; } from "antd";
import momentTZ from "moment-timezone";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import DatePickerRanges from "../../utils/DatePickerRanges";
import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import FormItemEmail from "../form-items-formatted/email-form-item.component"; import FormItemEmail from "../form-items-formatted/email-form-item.component";
import PhoneFormItem, { import PhoneFormItem, {
@@ -18,12 +25,22 @@ import PhoneFormItem, {
} from "../form-items-formatted/phone-form-item.component"; } from "../form-items-formatted/phone-form-item.component";
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import momentTZ from "moment-timezone";
const timeZonesList = momentTZ.tz.names(); const timeZonesList = momentTZ.tz.names();
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoGeneral);
export default function ShopInfoGeneral({ form }) { export function ShopInfoGeneral({ form, bodyshop }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { ClosingPeriod } = useTreatments(
["ClosingPeriod"],
{},
bodyshop && bodyshop.imexshopid
);
return ( return (
<div> <div>
@@ -171,20 +188,22 @@ export default function ShopInfoGeneral({ form }) {
<Switch /> <Switch />
</Form.Item> </Form.Item>
<Form.Item shouldUpdate noStyle> {
{() => ( // <Form.Item shouldUpdate noStyle>
<Form.Item // {() => (
label={t("bodyshop.labels.qbo_usa")} // <Form.Item
shouldUpdate // label={t("bodyshop.labels.qbo_usa")}
valuePropName="checked" // shouldUpdate
name={["accountingconfig", "qbo_usa"]} // valuePropName="checked"
> // name={["accountingconfig", "qbo_usa"]}
<Switch // >
disabled={!form.getFieldValue(["accountingconfig", "qbo"])} // <Switch
/> // disabled={!form.getFieldValue(["accountingconfig", "qbo"])}
</Form.Item> // />
)} // </Form.Item>
</Form.Item> // )}
// </Form.Item>
}
<Form.Item <Form.Item
label={t("bodyshop.labels.qbo_departmentid")} label={t("bodyshop.labels.qbo_departmentid")}
name={["accountingconfig", "qbo_departmentid"]} name={["accountingconfig", "qbo_departmentid"]}
@@ -273,36 +292,34 @@ export default function ShopInfoGeneral({ form }) {
> >
<InputNumber min={0} precision={2} /> <InputNumber min={0} precision={2} />
</Form.Item> </Form.Item>
<Form.Item {
label={t("bodyshop.fields.federal_tax_id")} // <Form.Item
name="federal_tax_id" // label={t("bodyshop.fields.federal_tax_id")}
rules={[ // name="federal_tax_id"
{ // >
required: true, // <Input />
//message: t("general.validation.required"), // </Form.Item>
}, }
]}
>
<Input />
</Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.state_tax_id")} label={t("bodyshop.fields.state_tax_id")}
name="state_tax_id" name="state_tax_id"
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item {
label={t("bodyshop.fields.invoice_federal_tax_rate")} // <Form.Item
name={["bill_tax_rates", "federal_tax_rate"]} // label={t("bodyshop.fields.invoice_federal_tax_rate")}
rules={[ // name={["bill_tax_rates", "federal_tax_rate"]}
{ // rules={[
required: true, // {
//message: t("general.validation.required"), // required: true,
}, // //message: t("general.validation.required"),
]} // },
> // ]}
<InputNumber /> // >
</Form.Item> // <InputNumber />
// </Form.Item>
}
<Form.Item <Form.Item
label={t("bodyshop.fields.invoice_state_tax_rate")} label={t("bodyshop.fields.invoice_state_tax_rate")}
name={["bill_tax_rates", "state_tax_rate"]} name={["bill_tax_rates", "state_tax_rate"]}
@@ -392,6 +409,20 @@ export default function ShopInfoGeneral({ form }) {
> >
<Select mode="tags" /> <Select mode="tags" />
</Form.Item> </Form.Item>
{ClosingPeriod.treatment === "on" && (
<>
<Form.Item
allowClear
name={["accountingconfig", "ClosingPeriod"]}
label={t("bodyshop.fields.closingperiod")} //{t("reportcenter.labels.dates")}
>
<DatePicker.RangePicker
format="MM/DD/YYYY"
ranges={DatePickerRanges}
/>
</Form.Item>
</>
)}
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow <LayoutFormRow
header={t("bodyshop.labels.scoreboardsetup")} header={t("bodyshop.labels.scoreboardsetup")}
@@ -604,7 +635,9 @@ export default function ShopInfoGeneral({ form }) {
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name={["md_email_cc", "parts_return_slip"]} name={["md_email_cc", "parts_return_slip"]}
label={t("bodyshop.fields.md_email_cc", { template: "parts_return_slip" })} label={t("bodyshop.fields.md_email_cc", {
template: "parts_return_slip",
})}
rules={[ rules={[
{ {
//message: t("general.validation.required"), //message: t("general.validation.required"),

View File

@@ -17,6 +17,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { useTreatments } from "@splitsoftware/splitio-react"; import { useTreatments } from "@splitsoftware/splitio-react";
import ShopInfoResponsibilitycentersTaxesComponent from "./shop-info.responsibilitycenters.taxes.component";
const SelectorDiv = styled.div` const SelectorDiv = styled.div`
.ant-form-item .ant-select { .ant-form-item .ant-select {
@@ -4116,122 +4117,124 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</LayoutFormRow> </LayoutFormRow>
</SelectorDiv> </SelectorDiv>
<LayoutFormRow {
header={t("bodyshop.labels.responsibilitycenters.tax_accounts")} // <LayoutFormRow
id="tax_accounts" // header={t("bodyshop.labels.responsibilitycenters.tax_accounts")}
> // id="tax_accounts"
<Form.Item // >
label={t("bodyshop.fields.responsibilitycenters.federal_tax")} // <Form.Item
rules={[ // label={t("bodyshop.fields.responsibilitycenters.federal_tax")}
{ // rules={[
required: true, // {
//message: t("general.validation.required"), // required: true,
}, // //message: t("general.validation.required"),
]} // },
name={["md_responsibility_centers", "taxes", "federal", "name"]} // ]}
> // name={["md_responsibility_centers", "taxes", "federal", "name"]}
<Input /> // >
</Form.Item> // <Input />
{/* <Form.Item // </Form.Item>
label={t("bodyshop.fields.responsibilitycenter_accountnumber")} // {/* <Form.Item
rules={[ // label={t("bodyshop.fields.responsibilitycenter_accountnumber")}
{ // rules={[
required: true, // {
//message: t("general.validation.required"), // required: true,
}, // //message: t("general.validation.required"),
]} // },
name={[ // ]}
"md_responsibility_centers", // name={[
"taxes", // "md_responsibility_centers",
"federal", // "taxes",
"accountnumber", // "federal",
]} // "accountnumber",
> // ]}
<Input /> // >
</Form.Item> */} // <Input />
{/* <Form.Item // </Form.Item> */}
label={t("bodyshop.fields.responsibilitycenter_accountname")} // {/* <Form.Item
rules={[ // label={t("bodyshop.fields.responsibilitycenter_accountname")}
{ // rules={[
required: true, // {
//message: t("general.validation.required"), // required: true,
}, // //message: t("general.validation.required"),
]} // },
name={[ // ]}
"md_responsibility_centers", // name={[
"taxes", // "md_responsibility_centers",
"federal", // "taxes",
"accountname", // "federal",
]} // "accountname",
> // ]}
<Input /> // >
</Form.Item> */} // <Input />
<Form.Item // </Form.Item> */}
label={t("bodyshop.fields.responsibilitycenter_accountdesc")} // <Form.Item
rules={[ // label={t("bodyshop.fields.responsibilitycenter_accountdesc")}
{ // rules={[
required: true, // {
//message: t("general.validation.required"), // required: true,
}, // //message: t("general.validation.required"),
]} // },
name={[ // ]}
"md_responsibility_centers", // name={[
"taxes", // "md_responsibility_centers",
"federal", // "taxes",
"accountdesc", // "federal",
]} // "accountdesc",
> // ]}
<Input /> // >
</Form.Item> // <Input />
<Form.Item // </Form.Item>
label={t("bodyshop.fields.responsibilitycenter_accountitem")} // <Form.Item
rules={[ // label={t("bodyshop.fields.responsibilitycenter_accountitem")}
{ // rules={[
required: true, // {
//message: t("general.validation.required"), // required: true,
}, // //message: t("general.validation.required"),
]} // },
name={[ // ]}
"md_responsibility_centers", // name={[
"taxes", // "md_responsibility_centers",
"federal", // "taxes",
"accountitem", // "federal",
]} // "accountitem",
> // ]}
<Input /> // >
</Form.Item> // <Input />
{(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( // </Form.Item>
<Form.Item // {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && (
label={t("bodyshop.fields.dms.dms_acctnumber")} // <Form.Item
rules={[ // label={t("bodyshop.fields.dms.dms_acctnumber")}
{ // rules={[
required: true, // {
//message: t("general.validation.required"), // required: true,
}, // //message: t("general.validation.required"),
]} // },
name={[ // ]}
"md_responsibility_centers", // name={[
"taxes", // "md_responsibility_centers",
"federal", // "taxes",
"dms_acctnumber", // "federal",
]} // "dms_acctnumber",
> // ]}
<Input /> // >
</Form.Item> // <Input />
)} // </Form.Item>
<Form.Item // )}
label={t("bodyshop.fields.responsibilitycenter_rate")} // <Form.Item
rules={[ // label={t("bodyshop.fields.responsibilitycenter_rate")}
{ // rules={[
required: true, // {
//message: t("general.validation.required"), // required: true,
}, // //message: t("general.validation.required"),
]} // },
name={["md_responsibility_centers", "taxes", "federal", "rate"]} // ]}
> // name={["md_responsibility_centers", "taxes", "federal", "rate"]}
<InputNumber precision={2} /> // >
</Form.Item> // <InputNumber precision={2} />
</LayoutFormRow> // </Form.Item>
// </LayoutFormRow>
}
{DmsAp.treatment === "on" && ( {DmsAp.treatment === "on" && (
<LayoutFormRow id="federal_tax_itc"> <LayoutFormRow id="federal_tax_itc">
<Form.Item <Form.Item
@@ -4347,104 +4350,107 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
)} )}
<LayoutFormRow id="state_tax"> {
<Form.Item // <LayoutFormRow id="state_tax">
label={t("bodyshop.fields.responsibilitycenters.state_tax")} // <Form.Item
rules={[ // label={t("bodyshop.fields.responsibilitycenters.state_tax")}
{ // rules={[
required: true, // {
//message: t("general.validation.required"), // required: true,
}, // //message: t("general.validation.required"),
]} // },
name={["md_responsibility_centers", "taxes", "state", "name"]} // ]}
> // name={["md_responsibility_centers", "taxes", "state", "name"]}
<Input /> // >
</Form.Item> // <Input />
{/* <Form.Item // </Form.Item>
label={t("bodyshop.fields.responsibilitycenter_accountnumber")} // {/* <Form.Item
rules={[ // label={t("bodyshop.fields.responsibilitycenter_accountnumber")}
{ // rules={[
required: true, // {
//message: t("general.validation.required"), // required: true,
}, // //message: t("general.validation.required"),
]} // },
name={[ // ]}
"md_responsibility_centers", // name={[
"taxes", // "md_responsibility_centers",
"state", // "taxes",
"accountnumber", // "state",
]} // "accountnumber",
> // ]}
<Input /> // >
</Form.Item> // <Input />
<Form.Item // </Form.Item>
label={t("bodyshop.fields.responsibilitycenter_accountname")} // <Form.Item
rules={[ // label={t("bodyshop.fields.responsibilitycenter_accountname")}
{ // rules={[
required: true, // {
//message: t("general.validation.required"), // required: true,
}, // //message: t("general.validation.required"),
]} // },
name={["md_responsibility_centers", "taxes", "state", "accountname"]} // ]}
> // name={["md_responsibility_centers", "taxes", "state", "accountname"]}
<Input /> // >
</Form.Item> */} // <Input />
<Form.Item // </Form.Item> */}
label={t("bodyshop.fields.responsibilitycenter_accountdesc")} // <Form.Item
rules={[ // label={t("bodyshop.fields.responsibilitycenter_accountdesc")}
{ // rules={[
required: true, // {
//message: t("general.validation.required"), // required: true,
}, // //message: t("general.validation.required"),
]} // },
name={["md_responsibility_centers", "taxes", "state", "accountdesc"]} // ]}
> // name={["md_responsibility_centers", "taxes", "state", "accountdesc"]}
<Input /> // >
</Form.Item> // <Input />
<Form.Item // </Form.Item>
label={t("bodyshop.fields.responsibilitycenter_accountitem")} // <Form.Item
rules={[ // label={t("bodyshop.fields.responsibilitycenter_accountitem")}
{ // rules={[
required: true, // {
//message: t("general.validation.required"), // required: true,
}, // //message: t("general.validation.required"),
]} // },
name={["md_responsibility_centers", "taxes", "state", "accountitem"]} // ]}
> // name={["md_responsibility_centers", "taxes", "state", "accountitem"]}
<Input /> // >
</Form.Item> // <Input />
{(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( // </Form.Item>
<Form.Item // {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && (
label={t("bodyshop.fields.dms.dms_acctnumber")} // <Form.Item
rules={[ // label={t("bodyshop.fields.dms.dms_acctnumber")}
{ // rules={[
required: true, // {
//message: t("general.validation.required"), // required: true,
}, // //message: t("general.validation.required"),
]} // },
name={[ // ]}
"md_responsibility_centers", // name={[
"taxes", // "md_responsibility_centers",
"state", // "taxes",
"dms_acctnumber", // "state",
]} // "dms_acctnumber",
> // ]}
<Input /> // >
</Form.Item> // <Input />
)} // </Form.Item>
<Form.Item // )}
label={t("bodyshop.fields.responsibilitycenter_rate")} // <Form.Item
rules={[ // label={t("bodyshop.fields.responsibilitycenter_rate")}
{ // rules={[
required: true, // {
//message: t("general.validation.required"), // required: true,
}, // //message: t("general.validation.required"),
]} // },
name={["md_responsibility_centers", "taxes", "state", "rate"]} // ]}
> // name={["md_responsibility_centers", "taxes", "state", "rate"]}
<InputNumber precision={2} /> // >
</Form.Item> // <InputNumber precision={2} />
</LayoutFormRow> // </Form.Item>
// </LayoutFormRow>
}
<ShopInfoResponsibilitycentersTaxesComponent form={form} />
<Form.Item <Form.Item
label={t("bodyshop.fields.responsibilitycenters.itemexemptcode")} label={t("bodyshop.fields.responsibilitycenters.itemexemptcode")}
rules={[ rules={[
@@ -4470,104 +4476,116 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<Input /> <Input />
</Form.Item> </Form.Item>
<LayoutFormRow id="local_tax"> {
<Form.Item // <LayoutFormRow id="local_tax">
label={t("bodyshop.fields.responsibilitycenters.local_tax")} // <Form.Item
rules={[ // label={t("bodyshop.fields.responsibilitycenters.local_tax")}
{ // rules={[
required: true, // {
//message: t("general.validation.required"), // required: true,
}, // //message: t("general.validation.required"),
]} // },
name={["md_responsibility_centers", "taxes", "local", "name"]} // ]}
> // name={["md_responsibility_centers", "taxes", "local", "name"]}
<Input /> // >
</Form.Item> // <Input />
{/* <Form.Item // </Form.Item>
label={t("bodyshop.fields.responsibilitycenter_accountnumber")} // {/* <Form.Item
rules={[ // label={t("bodyshop.fields.responsibilitycenter_accountnumber")}
{ // rules={[
required: true, // {
//message: t("general.validation.required"), // required: true,
}, // //message: t("general.validation.required"),
]} // },
name={[ // ]}
"md_responsibility_centers", // name={[
"taxes", // "md_responsibility_centers",
"local", // "taxes",
"accountnumber", // "local",
]} // "accountnumber",
> // ]}
<Input /> // >
</Form.Item> // <Input />
<Form.Item // </Form.Item>
label={t("bodyshop.fields.responsibilitycenter_accountname")} // <Form.Item
rules={[ // label={t("bodyshop.fields.responsibilitycenter_accountname")}
{ // rules={[
required: true, // {
//message: t("general.validation.required"), // required: true,
}, // //message: t("general.validation.required"),
]} // },
name={["md_responsibility_centers", "taxes", "local", "accountname"]} // ]}
> // name={["md_responsibility_centers", "taxes", "local", "accountname"]}
<Input /> // >
</Form.Item> */} // <Input />
<Form.Item // </Form.Item> */}
label={t("bodyshop.fields.responsibilitycenter_accountdesc")} // <Form.Item
rules={[ // label={t("bodyshop.fields.responsibilitycenter_accountdesc")}
{ // rules={[
required: true, // {
//message: t("general.validation.required"), // required: true,
}, // //message: t("general.validation.required"),
]} // },
name={["md_responsibility_centers", "taxes", "local", "accountdesc"]} // ]}
> // name={[
<Input /> // "md_responsibility_centers",
</Form.Item> // "taxes",
<Form.Item // "local",
label={t("bodyshop.fields.responsibilitycenter_accountitem")} // "accountdesc",
rules={[ // ]}
{ // >
required: true, // <Input />
//message: t("general.validation.required"), // </Form.Item>
}, // <Form.Item
]} // label={t("bodyshop.fields.responsibilitycenter_accountitem")}
name={["md_responsibility_centers", "taxes", "local", "accountitem"]} // rules={[
> // {
<Input /> // required: true,
</Form.Item> // //message: t("general.validation.required"),
{(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( // },
<Form.Item // ]}
label={t("bodyshop.fields.dms.dms_acctnumber")} // name={[
rules={[ // "md_responsibility_centers",
{ // "taxes",
required: true, // "local",
//message: t("general.validation.required"), // "accountitem",
}, // ]}
]} // >
name={[ // <Input />
"md_responsibility_centers", // </Form.Item>
"taxes", // {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && (
"local", // <Form.Item
"dms_acctnumber", // label={t("bodyshop.fields.dms.dms_acctnumber")}
]} // rules={[
> // {
<Input /> // required: true,
</Form.Item> // //message: t("general.validation.required"),
)} // },
<Form.Item // ]}
label={t("bodyshop.fields.responsibilitycenter_rate")} // name={[
rules={[ // "md_responsibility_centers",
{ // "taxes",
required: true, // "local",
//message: t("general.validation.required"), // "dms_acctnumber",
}, // ]}
]} // >
name={["md_responsibility_centers", "taxes", "local", "rate"]} // <Input />
> // </Form.Item>
<InputNumber precision={2} /> // )}
</Form.Item> // <Form.Item
</LayoutFormRow> // label={t("bodyshop.fields.responsibilitycenter_rate")}
// rules={[
// {
// required: true,
// //message: t("general.validation.required"),
// },
// ]}
// name={["md_responsibility_centers", "taxes", "local", "rate"]}
// >
// <InputNumber precision={2} />
// </Form.Item>
// </LayoutFormRow>
}
<LayoutFormRow header={<div>AR</div>} id="AR"> <LayoutFormRow header={<div>AR</div>} id="AR">
{/* <Form.Item {/* <Form.Item
label={t("bodyshop.fields.responsibilitycenters.ar")} label={t("bodyshop.fields.responsibilitycenters.ar")}

View File

@@ -1,12 +1,12 @@
import { LockOutlined, UserOutlined } from "@ant-design/icons"; import { LockOutlined, UserOutlined } from "@ant-design/icons";
import { Button, Form, Input, Typography } from "antd"; import { Button, Form, Input } from "antd";
import queryString from "query-string"; import queryString from "query-string";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link, Redirect, useLocation } from "react-router-dom"; import { Link, Redirect, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import RomeLogo from "../../assets/romelogo.png"; import RomeLogo from "../../assets/RomeOnlineBlue.png";
import { import {
emailSignInStart, emailSignInStart,
sendPasswordReset, sendPasswordReset,
@@ -54,7 +54,6 @@ export function SignInComponent({
<div className="login-container"> <div className="login-container">
<div className="login-logo-container"> <div className="login-logo-container">
<img src={RomeLogo} width={200} alt="Rome Online" /> <img src={RomeLogo} width={200} alt="Rome Online" />
<Typography.Title>{t("titles.app")}</Typography.Title>
</div> </div>
<Form onFinish={handleFinish} form={form} size="large"> <Form onFinish={handleFinish} form={form} size="large">
<Form.Item <Form.Item

View File

@@ -1,22 +1,27 @@
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, Card, Form, notification, Space } from "antd"; import { Button, Card, Form, notification, Space } from "antd";
import axios from "axios"; import axios from "axios";
import moment from "moment";
import momenttz from "moment-timezone";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { INSERT_NEW_TIME_TICKET } from "../../graphql/timetickets.queries"; import { INSERT_NEW_TIME_TICKET } from "../../graphql/timetickets.queries";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import TechClockInComponent from "./tech-job-clock-in-form.component";
import TechJobPrintTickets from "../tech-job-print-tickets/tech-job-print-tickets.component";
import moment from "moment";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import TechJobPrintTickets from "../tech-job-print-tickets/tech-job-print-tickets.component";
import TechClockInComponent from "./tech-job-clock-in-form.component";
import { useTreatments } from "@splitsoftware/splitio-react"; import { useTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
technician: selectTechnician, technician: selectTechnician,
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setTimeTicketContext: (context) => setTimeTicketContext: (context) =>
@@ -26,6 +31,7 @@ export function TechClockInContainer({
setTimeTicketContext, setTimeTicketContext,
technician, technician,
bodyshop, bodyshop,
currentUser,
}) { }) {
const { Enhanced_Payroll } = useTreatments( const { Enhanced_Payroll } = useTreatments(
["Enhanced_Payroll"], ["Enhanced_Payroll"],
@@ -46,14 +52,20 @@ export function TechClockInContainer({
const handleFinish = async (values) => { const handleFinish = async (values) => {
setLoading(true); setLoading(true);
const theTime = (await axios.post("/utils/time")).data; const theTime = (await axios.post("/utils/time")).data;
const result = await insertTimeTicket({ const result = await insertTimeTicket({
variables: { variables: {
timeTicketInput: [ timeTicketInput: [
{ {
bodyshopid: bodyshop.id, bodyshopid: bodyshop.id,
employeeid: technician.id, employeeid: technician.id,
date: moment(theTime).format("YYYY-MM-DD"), date:
typeof bodyshop.timezone === "string"
? momenttz.tz(theTime, bodyshop.timezone).format("YYYY-MM-DD")
: typeof bodyshop.timezone === "number"
? moment(theTime)
.format("YYYY-MM-DD")
.utcOffset(bodyshop.timezone)
: moment(theTime).format("YYYY-MM-DD"),
clockon: moment(theTime), clockon: moment(theTime),
jobid: values.jobid, jobid: values.jobid,
cost_center: values.cost_center, cost_center: values.cost_center,
@@ -68,6 +80,12 @@ export function TechClockInContainer({
values.cost_center values.cost_center
); );
}), }),
created_by: currentUser.email.concat(
" | ",
technician.employee_number
.concat(" ", technician.first_name, " ", technician.last_name)
.trim()
),
}, },
], ],
}, },
@@ -102,13 +120,24 @@ export function TechClockInContainer({
employeeid: technician.id, employeeid: technician.id,
flat_rate: emps.flat_rate, flat_rate: emps.flat_rate,
}, },
created_by: currentUser.email.concat(
" | ",
technician.employee_number
.concat(
" ",
technician.first_name,
" ",
technician.last_name
)
.trim()
),
}, },
}); });
}} }}
> >
{t("timetickets.actions.enter")} {t("timetickets.actions.enter")}
</Button> </Button>
<TechJobPrintTickets /> <TechJobPrintTickets attendacePrint={false} />
<Button <Button
type="primary" type="primary"
onClick={() => form.submit()} onClick={() => form.submit()}

View File

@@ -1,4 +1,4 @@
import { Button, Card, DatePicker, Form, Popover, Space } from "antd"; import { Button, Card, DatePicker, Form, Popover, Radio, Space } from "antd";
import moment from "moment"; import moment from "moment";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -21,12 +21,13 @@ export default connect(
mapDispatchToProps mapDispatchToProps
)(TechJobPrintTickets); )(TechJobPrintTickets);
export function TechJobPrintTickets({ technician, event }) { export function TechJobPrintTickets({ technician, event, attendacePrint }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [form] = Form.useForm(); const [form] = Form.useForm();
const [visibility, setVisibility] = useState(false); const [visibility, setVisibility] = useState(false);
const Templates = TemplateList("report_center");
useEffect(() => { useEffect(() => {
if (visibility && event) { if (visibility && event) {
@@ -44,7 +45,10 @@ export function TechJobPrintTickets({ technician, event }) {
try { try {
await GenerateDocument( await GenerateDocument(
{ {
name: TemplateList().timetickets_employee.key, name:
attendacePrint === true
? Templates.attendance_employee.key
: Templates.timetickets_employee.key,
variables: { variables: {
...(start ...(start
? { start: moment(start).startOf("day").format("YYYY-MM-DD") } ? { start: moment(start).startOf("day").format("YYYY-MM-DD") }
@@ -60,9 +64,12 @@ export function TechJobPrintTickets({ technician, event }) {
}, },
{ {
to: technician.email, to: technician.email,
subject: TemplateList().timetickets_employee.subject, subject:
attendacePrint === true
? Templates.attendance_employee.subject
: Templates.timetickets_employee.subject,
}, },
"p" values.sendby // === "email" ? "e" : "p"
); );
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@@ -92,10 +99,25 @@ export function TechJobPrintTickets({ technician, event }) {
format={"MM/DD/YYYY"} format={"MM/DD/YYYY"}
/> />
</Form.Item> </Form.Item>
<Form.Item dependencies={["dates"]}>
{() => {
return (
<Form.Item
label={t("general.labels.sendby")}
name="sendby"
initialValue="p"
>
<Radio.Group>
<Radio value="e">{t("general.labels.email")}</Radio>
<Radio value="p">{t("general.labels.print")}</Radio>
</Radio.Group>
</Form.Item>
);
}}
</Form.Item>
<Space wrap> <Space wrap>
<Button type="primary" onClick={() => form.submit()}> <Button type="primary" onClick={() => form.submit()}>
{t("general.actions.print")} {t("reportcenter.actions.generate")}
</Button> </Button>
<Button <Button
onClick={() => { onClick={() => {
@@ -118,7 +140,7 @@ export function TechJobPrintTickets({ technician, event }) {
return ( return (
<Popover content={overlay} visible={visibility}> <Popover content={overlay} visible={visibility}>
<Button loading={loading} onClick={handleClick}> <Button loading={loading} onClick={handleClick}>
{t("general.actions.print")} {t("general.labels.reports")}
</Button> </Button>
</Popover> </Popover>
); );

View File

@@ -2,6 +2,7 @@ import Icon, {
SearchOutlined, SearchOutlined,
ScheduleOutlined, ScheduleOutlined,
UserAddOutlined, UserAddOutlined,
CarOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { Layout, Menu } from "antd"; import { Layout, Menu } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
@@ -113,6 +114,15 @@ export function TechSider({
<Menu.Item key="5" disabled={!!!technician} icon={<ScheduleOutlined />}> <Menu.Item key="5" disabled={!!!technician} icon={<ScheduleOutlined />}>
<Link to={`/tech/list`}>{t("menus.tech.productionlist")}</Link> <Link to={`/tech/list`}>{t("menus.tech.productionlist")}</Link>
</Menu.Item> </Menu.Item>
<Menu.Item
key="dispatchedparts"
disabled={!!!technician}
icon={<CarOutlined />}
>
<Link to={`/tech/dispatchedparts`}>
{t("menus.tech.dispatchedparts")}
</Link>
</Menu.Item>
<Menu.Item <Menu.Item
key="6" key="6"
disabled={!!!technician} disabled={!!!technician}

View File

@@ -10,6 +10,7 @@ import { setModalContext } from "../../redux/modals/modals.actions";
import { import {
selectAuthLevel, selectAuthLevel,
selectBodyshop, selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter"; import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
import { onlyUnique } from "../../utils/arrayHelper"; import { onlyUnique } from "../../utils/arrayHelper";
@@ -23,6 +24,7 @@ import { useTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
authLevel: selectAuthLevel, authLevel: selectAuthLevel,
currentUser: selectCurrentUser,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setTimeTicketTaskContext: (context) => setTimeTicketTaskContext: (context) =>
@@ -34,6 +36,7 @@ export function TimeTicketList({
bodyshop, bodyshop,
setTimeTicketTaskContext, setTimeTicketTaskContext,
authLevel, authLevel,
currentUser,
disabled, disabled,
loading, loading,
timetickets, timetickets,
@@ -238,7 +241,15 @@ export function TimeTicketList({
}, },
}, },
]), ]),
{
title: t("timetickets.fields.created_by"),
dataIndex: "created_by",
key: "created_by",
sorter: (a, b) => alphaSort(a.created_by, b.created_by),
sortOrder:
state.sortedInfo.columnKey === "created_by" && state.sortedInfo.order,
render: (text, record) => record.created_by,
},
// { // {
// title: "Pay", // title: "Pay",
// dataIndex: "pay", // dataIndex: "pay",
@@ -326,7 +337,12 @@ export function TimeTicketList({
(techConsole ? null : ( (techConsole ? null : (
<TimeTicketEnterButton <TimeTicketEnterButton
actions={{ refetch }} actions={{ refetch }}
context={{ jobId: jobId }} context={{
jobId: jobId,
created_by: currentUser.displayName
? currentUser.email.concat(" | ", currentUser.displayName)
: currentUser.email,
}}
disabled={disabled} disabled={disabled}
> >
{t("timetickets.actions.enter")} {t("timetickets.actions.enter")}

View File

@@ -1,5 +1,5 @@
import { useMutation, useQuery } from "@apollo/client"; import { useMutation, useQuery } from "@apollo/client";
import { Button, Form, Modal, notification, PageHeader, Space } from "antd"; import { Button, Form, Modal, PageHeader, Space, notification } from "antd";
import moment from "moment"; import moment from "moment";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -83,6 +83,7 @@ export function TimeTicketModalContainer({
)[0].rate )[0].rate
: null, : null,
bodyshopid: bodyshop.id, bodyshopid: bodyshop.id,
created_by: timeTicketModal.context.created_by,
}, },
], ],
}, },

View File

@@ -4,48 +4,67 @@ import { useTranslation } from "react-i18next";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import DataLabel from "../data-label/data-label.component"; import DataLabel from "../data-label/data-label.component";
import TechClockOffButton from "../tech-job-clock-out-button/tech-job-clock-out-button.component"; import TechClockOffButton from "../tech-job-clock-out-button/tech-job-clock-out-button.component";
import TechJobPrintTickets from "../tech-job-print-tickets/tech-job-print-tickets.component";
export default function TimeTicketShiftActive({ timetickets, refetch }) { export default function TimeTicketShiftActive({
timetickets,
refetch,
isTechConsole,
}) {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div> <div>
{timetickets.length > 0 ? ( {timetickets.length > 0 ? (
<div> <div
<Typography.Title level={2}> style={{
{t("timetickets.labels.shiftalreadyclockedon")} display: "flex",
</Typography.Title> justifyContent: "space-between",
<List flexDirection: "column",
grid={{ height: "100%",
gutter: 32, }}
xs: 1, >
sm: 2, <div style={{ display: "flex", justifyContent: "space-between" }}>
md: 3, <Typography.Title level={2}>
lg: 4, {t("timetickets.labels.shiftalreadyclockedon")}
xl: 5, </Typography.Title>
xxl: 6, {isTechConsole ? (
}} <TechJobPrintTickets attendacePrint={true} />
dataSource={timetickets || []} ) : null}
renderItem={(ticket) => ( </div>
<List.Item> <div style={{ flexGrow: 1 }}>
<Card <List
title={t(ticket.memo)} grid={{
actions={[ gutter: 32,
<TechClockOffButton xs: 1,
jobId={ticket.jobid} sm: 2,
timeTicketId={ticket.id} md: 3,
completedCallback={refetch} lg: 4,
isShiftTicket xl: 5,
/>, xxl: 6,
]} }}
> dataSource={timetickets || []}
<DataLabel label={t("timetickets.fields.clockon")}> renderItem={(ticket) => (
<DateTimeFormatter>{ticket.clockon}</DateTimeFormatter> <List.Item>
</DataLabel> <Card
</Card> title={t(ticket.memo)}
</List.Item> actions={[
)} <TechClockOffButton
></List> jobId={ticket.jobid}
timeTicketId={ticket.id}
completedCallback={refetch}
isShiftTicket
/>,
]}
>
<DataLabel label={t("timetickets.fields.clockon")}>
<DateTimeFormatter>{ticket.clockon}</DateTimeFormatter>
</DataLabel>
</Card>
</List.Item>
)}
></List>
</div>
</div> </div>
) : null} ) : null}
</div> </div>

View File

@@ -1,7 +1,8 @@
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, Form, notification } from "antd"; import { Button, Form, Space, notification } from "antd";
import axios from "axios"; import axios from "axios";
import moment from "moment"; import moment from "moment";
import momenttz from "moment-timezone";
import React, { useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -12,6 +13,7 @@ import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import TechJobPrintTickets from "../tech-job-print-tickets/tech-job-print-tickets.component";
import TimeTicektShiftComponent from "./time-ticket-shift-form.component"; import TimeTicektShiftComponent from "./time-ticket-shift-form.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
@@ -29,6 +31,10 @@ export function TimeTicektShiftContainer({
isTechConsole, isTechConsole,
checkIfAlreadyClocked, checkIfAlreadyClocked,
}) { }) {
console.log(
"🚀 ~ file: time-ticket-shift-form.container.jsx:28 ~ technician:",
technician
);
const [form] = Form.useForm(); const [form] = Form.useForm();
const [insertTimeTicket] = useMutation(INSERT_NEW_TIME_TICKET); const [insertTimeTicket] = useMutation(INSERT_NEW_TIME_TICKET);
const { t } = useTranslation(); const { t } = useTranslation();
@@ -63,8 +69,30 @@ export function TimeTicektShiftContainer({
employeeid: isTechConsole ? technician.id : employeeId, employeeid: isTechConsole ? technician.id : employeeId,
cost_center: "timetickets.labels.shift", cost_center: "timetickets.labels.shift",
clockon: theTime, clockon: theTime,
date: theTime, date:
typeof bodyshop.timezone === "string"
? momenttz.tz(theTime, bodyshop.timezone).format("YYYY-MM-DD")
: typeof bodyshop.timezone === "number"
? moment(theTime)
.utcOffset(bodyshop.timezone)
.format("YYYY-MM-DD")
: moment(theTime).format("YYYY-MM-DD"),
memo: values.memo, memo: values.memo,
created_by: isTechConsole
? currentUser.email.concat(
" | ",
technician.employee_number
.concat(
" ",
technician.first_name,
" ",
technician.last_name
)
.trim()
)
: currentUser.displayName
? currentUser.email.concat(" | ", currentUser.displayName)
: currentUser.email,
}, },
], ],
}, },
@@ -98,9 +126,14 @@ export function TimeTicektShiftContainer({
initialValues={{ cost_center: t("timetickets.labels.shift") }} initialValues={{ cost_center: t("timetickets.labels.shift") }}
> >
<TimeTicektShiftComponent form={form} /> <TimeTicektShiftComponent form={form} />
<Button htmlType="submit" loading={loading}> <Space wrap>
{t("timetickets.actions.clockin")} <Button htmlType="submit" loading={loading} type="primary">
</Button> {t("timetickets.actions.clockin")}
</Button>
{isTechConsole === true ? (
<TechJobPrintTickets attendacePrint={true} />
) : null}
</Space>
</Form> </Form>
</div> </div>
); );

View File

@@ -76,6 +76,7 @@ export function TimeTicketShiftContainer({
<TimeTicketShiftActive <TimeTicketShiftActive
timetickets={data ? data.timetickets : []} timetickets={data ? data.timetickets : []}
refetch={refetch} refetch={refetch}
isTechConsole={isTechConsole}
/> />
) : ( ) : (
<TimeTicketShiftFormContainer <TimeTicketShiftFormContainer

View File

@@ -6,8 +6,9 @@ import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import VehicleDetailUpdateJobsComponent from "../vehicle-detail-update-jobs/vehicle-detail-update-jobs.component"; import { alphaSort, statusSort } from "../../utils/sorters";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import VehicleDetailUpdateJobsComponent from "../vehicle-detail-update-jobs/vehicle-detail-update-jobs.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -16,6 +17,14 @@ const mapStateToProps = createStructuredSelector({
export function VehicleDetailJobsComponent({ vehicle, bodyshop }) { export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [selectedJobs, setSelectedJobs] = useState([]); const [selectedJobs, setSelectedJobs] = useState([]);
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: { text: "" },
});
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const columns = [ const columns = [
{ {
@@ -28,6 +37,9 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
{record.ro_number || t("general.labels.na")} {record.ro_number || t("general.labels.na")}
</Link> </Link>
), ),
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
}, },
{ {
title: t("jobs.fields.owner"), title: t("jobs.fields.owner"),
@@ -43,11 +55,17 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
title: t("jobs.fields.clm_no"), title: t("jobs.fields.clm_no"),
dataIndex: "clm_no", dataIndex: "clm_no",
key: "clm_no", key: "clm_no",
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder:
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
}, },
{ {
title: t("jobs.fields.status"), title: t("jobs.fields.status"),
dataIndex: "status", dataIndex: "status",
key: "status", key: "status",
sorter: (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses),
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
}, },
{ {
@@ -57,6 +75,9 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
render: (text, record) => ( render: (text, record) => (
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter> <CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
), ),
sorter: (a, b) => a.clm_total - b.clm_total,
sortOrder:
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
}, },
]; ];
@@ -76,6 +97,7 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
rowKey="id" rowKey="id"
scroll={{ x: true }} scroll={{ x: true }}
dataSource={vehicle.jobs} dataSource={vehicle.jobs}
onChange={handleTableChange}
rowSelection={{ rowSelection={{
onSelect: (record, selected, selectedRows) => { onSelect: (record, selected, selectedRows) => {
setSelectedJobs(selectedRows ? selectedRows.map((i) => i.id) : []); setSelectedJobs(selectedRows ? selectedRows.map((i) => i.id) : []);

View File

@@ -57,6 +57,7 @@ export const GET_LINE_TICKET_BY_PK = gql`
actualhrs actualhrs
ciecacode ciecacode
cost_center cost_center
created_by
date date
id id
jobid jobid

View File

@@ -574,7 +574,6 @@ export const GET_JOB_BY_PK = gql`
est_co_nm est_co_nm
est_ct_fn est_ct_fn
est_ct_ln est_ct_ln
est_ph1 est_ph1
est_ea est_ea
selling_dealer selling_dealer
@@ -756,8 +755,8 @@ export const GET_JOB_BY_PK = gql`
jobid jobid
amount amount
payer payer
paymentnum
created_at created_at
stripeid
transactionid transactionid
memo memo
date date
@@ -780,6 +779,10 @@ export const GET_JOB_BY_PK = gql`
} }
} }
cieca_ttl cieca_ttl
cieca_pfo
cieca_pfl
cieca_pft
materials
csiinvites { csiinvites {
id id
completedon completedon
@@ -913,6 +916,18 @@ export const QUERY_JOB_CARD_DETAILS = gql`
ioucreated ioucreated
convertedtolbr convertedtolbr
critical critical
parts_dispatch_lines {
id
accepted_at
quantity
parts_dispatch {
id
employeeid
dispatched_at
dispatched_by
number
}
}
} }
owner { owner {
id id
@@ -2181,6 +2196,18 @@ export const GET_JOB_LINE_ORDERS = gql`
} }
} }
} }
parts_dispatch_lines(where: { joblineid: { _eq: $joblineid } }) {
id
accepted_at
quantity
parts_dispatch {
id
employeeid
dispatched_at
dispatched_by
number
}
}
parts_order_lines(where: { job_line_id: { _eq: $joblineid } }) { parts_order_lines(where: { job_line_id: { _eq: $joblineid } }) {
id id
act_price act_price

View File

@@ -69,7 +69,7 @@ export const QUERY_OWNER_BY_ID = gql`
preferred_contact preferred_contact
note note
tax_number tax_number
jobs { jobs(order_by: { date_open: desc }) {
id id
ro_number ro_number
clm_no clm_no

View File

@@ -15,3 +15,65 @@ export const INSERT_PARTS_DISPATCH = gql`
} }
} }
`; `;
export const GET_UNACCEPTED_PARTS_DISPATCH = gql`
query GET_UNACCEPTED_PARTS_DISPATCH(
$techId: uuid!
$offset: Int
$limit: Int
) {
parts_dispatch_aggregate(
where: {
employeeid: { _eq: $techId }
parts_dispatch_lines: { accepted_at: { _is_null: true } }
}
) {
aggregate {
count(distinct: true)
}
}
parts_dispatch(
offset: $offset
limit: $limit
where: {
employeeid: { _eq: $techId }
parts_dispatch_lines: { accepted_at: { _is_null: true } }
}
) {
id
job {
id
ro_number
status
v_make_desc
v_model_desc
v_model_yr
v_color
}
dispatched_at
dispatched_by
parts_dispatch_lines {
id
accepted_at
jobline {
line_desc
id
}
quantity
joblineid
}
}
}
`;
export const UPDATE_PARTS_DISPATCH_LINE = gql`
mutation UPDATE_PARTS_DISPATCH_LINE(
$id: uuid!
$line: parts_dispatch_lines_set_input!
) {
update_parts_dispatch_lines_by_pk(pk_columns: { id: $id }, _set: $line) {
accepted_at
id
}
}
`;

View File

@@ -28,16 +28,14 @@ export const QUERY_PAYMENT_RESPONSE_BY_PAYMENT_ID = gql`
} }
`; `;
export const QUERY_RO_AND_OWNER_BY_JOB_PK = gql` export const QUERY_RO_AND_OWNER_BY_JOB_PKS = gql`
query QUERY_RO_AND_OWNER_BY_JOB_PK($jobid: uuid!) { query QUERY_RO_AND_OWNER_BY_JOB_PKS($jobids: [uuid!]!) {
jobs_by_pk(id: $jobid) { jobs(where: { id: { _in: $jobids } }) {
ro_number ro_number
owner { ownr_fn
ownr_fn ownr_ln
ownr_ln ownr_ea
ownr_ea ownr_zip
ownr_zip
}
} }
} }
`; `;

View File

@@ -5,6 +5,15 @@ export const INSERT_NEW_PAYMENT = gql`
insert_payments(objects: $paymentInput) { insert_payments(objects: $paymentInput) {
returning { returning {
id id
jobid
amount
payer
created_at
transactionid
memo
date
type
exportedat
} }
} }
} }

View File

@@ -39,6 +39,7 @@ export const QUERY_TIME_TICKETS_IN_RANGE = gql`
clockon clockon
cost_center cost_center
created_at created_at
created_by
date date
id id
rate rate
@@ -85,6 +86,7 @@ export const QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE = gql`
clockon clockon
cost_center cost_center
created_at created_at
created_by
date date
id id
rate rate
@@ -119,6 +121,7 @@ export const QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE = gql`
clockon clockon
cost_center cost_center
created_at created_at
created_by
date date
id id
rate rate
@@ -160,6 +163,7 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
clockon clockon
cost_center cost_center
created_at created_at
created_by
date date
id id
rate rate
@@ -194,6 +198,7 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
clockon clockon
cost_center cost_center
created_at created_at
created_by
date date
id id
rate rate
@@ -223,6 +228,7 @@ export const INSERT_NEW_TIME_TICKET = gql`
insert_timetickets(objects: $timeTicketInput) { insert_timetickets(objects: $timeTicketInput) {
returning { returning {
id id
created_by
clockon clockon
clockoff clockoff
employeeid employeeid

View File

@@ -28,11 +28,10 @@ export const QUERY_VEHICLE_BY_ID = gql`
updated_at updated_at
trim_color trim_color
notes notes
jobs { jobs(order_by: { date_open: desc }) {
id id
ro_number ro_number
ownr_fn ownr_fn
ownr_ln ownr_ln
owner { owner {
id id

View File

@@ -3,7 +3,7 @@ import ImexOnlineLogoLight from "../assets/ImEX Online Logo.png";
import i18n from "../translations/i18n"; import i18n from "../translations/i18n";
import TechnologySvg from "../assets/icons/technology.svg"; import TechnologySvg from "../assets/icons/technology.svg";
import RomeLogo from "../assets/romelogo.png"; import RomeLogo from "../assets/RomeOnline.png";
export const Nav00DataSource = { export const Nav00DataSource = {
wrapper: { className: "header0 home-page-wrapper" }, wrapper: { className: "header0 home-page-wrapper" },
page: { className: "home-page" }, page: { className: "home-page" },

View File

@@ -43,10 +43,10 @@ const mapDispatchToProps = (dispatch) => ({
export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer); export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer);
export const socket = SocketIO( export const socket = SocketIO(
process.env.NODE_ENV === "production" // process.env.NODE_ENV === "production"
? process.env.REACT_APP_AXIOS_BASE_API_URL // ? process.env.REACT_APP_AXIOS_BASE_API_URL
: window.location.origin, // : window.location.origin,
// "http://localhost:4000", // for dev testing, "http://localhost:4000", // for dev testing,
{ {
path: "/ws", path: "/ws",
withCredentials: true, withCredentials: true,

View File

@@ -8,7 +8,6 @@ import {
Form, Form,
Input, Input,
InputNumber, InputNumber,
notification,
PageHeader, PageHeader,
Popconfirm, Popconfirm,
Row, Row,
@@ -17,12 +16,14 @@ import {
Statistic, Statistic,
Switch, Switch,
Typography, Typography,
notification,
} from "antd"; } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
//import { useHistory } from "react-router-dom"; //import { useHistory } from "react-router-dom";
import { useTreatments } from "@splitsoftware/splitio-react"; import { useTreatments } from "@splitsoftware/splitio-react";
import Dinero from "dinero.js";
import moment from "moment"; import moment from "moment";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -37,7 +38,6 @@ import { generateJobLinesUpdatesForInvoicing } from "../../graphql/jobs-lines.qu
import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import Dinero from "dinero.js";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
@@ -55,6 +55,11 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
{}, {},
bodyshop && bodyshop.imexshopid bodyshop && bodyshop.imexshopid
); );
const { ClosingPeriod } = useTreatments(
["ClosingPeriod"],
{},
bodyshop && bodyshop.imexshopid
);
const handleFinish = async ({ removefromproduction, ...values }) => { const handleFinish = async ({ removefromproduction, ...values }) => {
setLoading(true); setLoading(true);
@@ -254,12 +259,40 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
if (!value || moment(value).isSameOrAfter(moment(), "day")) { if (!value || moment(value).isSameOrAfter(moment(), "day")) {
return Promise.resolve(); return Promise.resolve();
} }
return Promise.reject( return Promise.reject(
new Error(t("jobs.labels.dms.invoicedatefuture")) new Error(t("jobs.labels.dms.invoicedatefuture"))
); );
}, },
}), }),
({ getFieldValue }) => ({
validator(_, value) {
if (
ClosingPeriod.treatment === "on" &&
bodyshop.accountingconfig.ClosingPeriod
) {
if (
moment(value).isSameOrAfter(
moment(
bodyshop.accountingconfig.ClosingPeriod[0]
).startOf("day")
) &&
moment(value).isSameOrBefore(
moment(
bodyshop.accountingconfig.ClosingPeriod[1]
).endOf("day")
)
) {
return Promise.resolve();
} else {
return Promise.reject(
new Error(t("jobs.labels.closingperiod"))
);
}
} else {
return Promise.resolve();
}
},
}),
]} ]}
> >
<DateTimePicker <DateTimePicker

View File

@@ -169,96 +169,16 @@ function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
federal_tax_rate: bodyshop.bill_tax_rates.federal_tax_rate / 100, federal_tax_rate: bodyshop.bill_tax_rates.federal_tax_rate / 100,
state_tax_rate: bodyshop.bill_tax_rates.state_tax_rate / 100, state_tax_rate: bodyshop.bill_tax_rates.state_tax_rate / 100,
local_tax_rate: bodyshop.bill_tax_rates.local_tax_rate / 100, local_tax_rate: bodyshop.bill_tax_rates.local_tax_rate / 100,
parts_tax_rates: { cieca_pft: {
PAA: { ...bodyshop.md_responsibility_centers.taxes.tax_ty1,
prt_type: "PAA", ...bodyshop.md_responsibility_centers.taxes.tax_ty2,
prt_discp: 0, ...bodyshop.md_responsibility_centers.taxes.tax_ty3,
prt_mktyp: false, ...bodyshop.md_responsibility_centers.taxes.tax_ty4,
prt_mkupp: 0, ...bodyshop.md_responsibility_centers.taxes.tax_ty5,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAC: {
prt_type: "PAC",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAG: {
prt_type: "PAG",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAL: {
prt_type: "PAL",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAM: {
prt_type: "PAM",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAN: {
prt_type: "PAN",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAR: {
prt_type: "PAR",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAS: {
prt_type: "PAS",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PASL: {
prt_type: "PASL",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAP: {
prt_type: "PAP",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
PAO: {
prt_type: "PAO",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
},
}, },
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,
}} }}
> >
<JobsCreateComponent form={form} /> <JobsCreateComponent form={form} />

View File

@@ -53,6 +53,8 @@ import { insertAuditTrail } from "../../redux/application/application.actions";
import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container"; import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container";
import UndefinedToNull from "../../utils/undefinedtonull"; import UndefinedToNull from "../../utils/undefinedtonull";
import NoteUpsertModalComponent from "../../components/note-upsert-modal/note-upsert-modal.container"; import NoteUpsertModalComponent from "../../components/note-upsert-modal/note-upsert-modal.container";
import _ from "lodash";
import JobProfileDataWarning from "../../components/job-profile-data-warning/job-profile-data-warning.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -103,86 +105,121 @@ export function JobsDetailPage({
"category", "category",
"referral_source", "referral_source",
]), ]),
parts_tax_rates: { // The union and spread is required to keep values coming in from the estimating system that aren't displayed.
...job.parts_tax_rates, parts_tax_rates: _.union(
...values.parts_tax_rates, Object.keys(job.parts_tax_rates),
}, Object.keys(values.parts_tax_rates)
).reduce((acc, val) => {
acc[val] = {
...job.parts_tax_rates[val],
...values.parts_tax_rates[val],
};
return acc;
}, {}),
materials: _.union(
Object.keys(job.materials),
Object.keys(values.materials)
).reduce((acc, val) => {
acc[val] = {
...job.materials[val],
...values.materials[val],
};
return acc;
}, {}),
cieca_pfl: _.union(
Object.keys(job.cieca_pfl),
Object.keys(values.cieca_pfl)
).reduce((acc, val) => {
acc[val] = {
...job.cieca_pfl[val],
...values.cieca_pfl[val],
};
return acc;
}, {}),
cieca_pfo: { ...job.cieca_pfo, ...values.cieca_pfo },
}, },
}, },
}); });
const newTotals = await Axios.post("/job/totalsssu", { try {
id: job.id, const newTotals = await Axios.post("/job/totalsssu", {
}); id: job.id,
});
if (newTotals.status !== 200 || result.errors) { if (newTotals.status !== 200 || result.errors) {
notification["error"]({
message: t("jobs.errors.totalscalc"),
});
} else {
notification["success"]({
message: t("jobs.successes.savetitle"),
});
const changedAuditFields = form.getFieldsValue(
[
"scheduled_in",
"actual_in",
"scheduled_completion",
"actual_completion",
"scheduled_delivery",
"actual_delivery",
"date_invoiced",
"ins_co_nm",
"ded_amt",
"ded_status",
"date_exported",
"special_coverage_policy",
"ca_gst_registrant",
"ca_bc_pvrt",
"scheduled_in",
"rate_la1",
"rate_la2",
"rate_la3",
"rate_la4",
"rate_laa",
"rate_lab",
"rate_lad",
"rate_lae",
"rate_laf",
"rate_lag",
"rate_lam",
"rate_lar",
"rate_las",
"rate_lau",
"rate_ma2s",
"rate_ma2t",
"rate_ma3s",
"rate_mabl",
"rate_macs",
"rate_mapa",
"rate_mahw",
"rate_mash",
"rate_matd",
],
(meta) => meta && meta.touched
);
Object.keys(changedAuditFields).forEach((key) => {
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobfieldchange(
key,
changedAuditFields[key] instanceof moment
? moment(changedAuditFields[key]).format("MM/DD/YYYY hh:mm a")
: changedAuditFields[key]
),
});
});
await refetch();
form.setFieldsValue(transormJobToForm(job));
form.resetFields();
}
} catch (error) {
notification["error"]({ notification["error"]({
message: t("jobs.errors.totalscalc"), message: t("jobs.errors.totalscalc"),
}); });
} else { } finally {
notification["success"]({ setLoading(false);
message: t("jobs.successes.savetitle"),
});
const changedAuditFields = form.getFieldsValue(
[
"scheduled_in",
"actual_in",
"scheduled_completion",
"actual_completion",
"scheduled_delivery",
"actual_delivery",
"date_invoiced",
"ins_co_nm",
"ded_amt",
"ded_status",
"date_exported",
"special_coverage_policy",
"ca_gst_registrant",
"ca_bc_pvrt",
"scheduled_in",
"rate_la1",
"rate_la2",
"rate_la3",
"rate_la4",
"rate_laa",
"rate_lab",
"rate_lad",
"rate_lae",
"rate_laf",
"rate_lag",
"rate_lam",
"rate_lar",
"rate_las",
"rate_lau",
"rate_ma2s",
"rate_ma2t",
"rate_ma3s",
"rate_mabl",
"rate_macs",
"rate_mapa",
"rate_mahw",
"rate_mash",
"rate_matd",
],
(meta) => meta && meta.touched
);
Object.keys(changedAuditFields).forEach((key) => {
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobfieldchange(
key,
changedAuditFields[key] instanceof moment
? moment(changedAuditFields[key]).format("MM/DD/YYYY hh:mm a")
: changedAuditFields[key]
),
});
});
await refetch();
form.setFieldsValue(transormJobToForm(job));
form.resetFields();
} }
setLoading(false);
}; };
const menuExtra = ( const menuExtra = (
@@ -252,6 +289,7 @@ export function JobsDetailPage({
/> />
<JobsDetailHeader job={job} /> <JobsDetailHeader job={job} />
<Divider type="horizontal" /> <Divider type="horizontal" />
<JobProfileDataWarning job={job} />
<FormFieldsChanged form={form} /> <FormFieldsChanged form={form} />
<Tabs <Tabs
defaultActiveKey={search.tab} defaultActiveKey={search.tab}
@@ -389,6 +427,21 @@ export function JobsDetailPage({
export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailPage); export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailPage);
const transormJobToForm = (job) => { const transormJobToForm = (job) => {
Object.keys(job.parts_tax_rates).forEach((parttype) => {
Object.keys(job.parts_tax_rates[parttype]).forEach((key) => {
if (key.includes("tx_in")) {
if (
job.parts_tax_rates[parttype][key] === "Y" ||
job.parts_tax_rates[parttype][key] === true
) {
job.parts_tax_rates[parttype][key] = true;
} else {
job.parts_tax_rates[parttype][key] = false;
}
}
});
});
return { return {
...job, ...job,
loss_date: job.loss_date ? moment(job.loss_date) : null, loss_date: job.loss_date ? moment(job.loss_date) : null,

View File

@@ -0,0 +1,139 @@
import {
MinusCircleTwoTone,
PlusCircleTwoTone,
SyncOutlined,
} from "@ant-design/icons";
import { useQuery } from "@apollo/client";
import { Button, Card, Space, Table } from "antd";
import queryString from "query-string";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import AlertComponent from "../../components/alert/alert.component";
import PartsDispatchExpander from "../../components/parts-dispatch-expander/parts-dispatch-expander.component";
import { GET_UNACCEPTED_PARTS_DISPATCH } from "../../graphql/parts-dispatch.queries";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { alphaSort } from "../../utils/sorters";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
technician: selectTechnician,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({});
export function TechDispatchedParts({ technician, bodyshop }) {
const searchParams = queryString.parse(useLocation().search);
const { page } = searchParams;
const { loading, error, data, refetch } = useQuery(
GET_UNACCEPTED_PARTS_DISPATCH,
{
variables: {
techId: technician.id,
offset: page ? (page - 1) * 25 : 0,
limit: 25,
},
}
);
const { t } = useTranslation();
const history = useHistory();
if (error) return <AlertComponent message={error.message} type="error" />;
const parts_dispatch = data?.parts_dispatch;
const columns = [
{
title: t("jobs.fields.ro_number"),
dataIndex: "job.ro_number",
key: "ro_number",
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
render: (text, record) => record.job.ro_number || t("general.labels.na"),
},
{
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status",
sorter: (a, b) => alphaSort(a.status, b.status),
render: (text, record) => {
return record.job.status || t("general.labels.na");
},
},
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
render: (text, record) => (
<span>{`${record.job.v_model_yr || ""} ${
record.job.v_make_desc || ""
} ${record.job.v_model_desc || ""}`}</span>
),
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
render: (text, record) => (
<Button onClick={() => {}}>
{t("timetickets.actions.claimtasks")}
</Button>
),
},
];
const handleTableChange = (pagination, filters, sorter) => {
searchParams.page = pagination.current;
history.push({ search: queryString.stringify(searchParams) });
};
return (
<Card
extra={
<Space wrap>
<Button onClick={() => refetch()}>
<SyncOutlined />
</Button>
</Space>
}
>
<Table
loading={loading}
pagination={{
pageSize: 25,
current: parseInt(page || 1),
total: data ? data.parts_dispatch_aggregate.aggregate.count : 0,
showSizeChanger: false,
}}
columns={columns}
rowKey="id"
dataSource={parts_dispatch}
scroll={{ x: true }}
onChange={handleTableChange}
expandable={{
expandedRowRender: (record) => (
<PartsDispatchExpander dispatch={record} />
),
rowExpandable: (record) => true,
//expandRowByClick: true,
expandIcon: ({ expanded, onExpand, record }) =>
expanded ? (
<MinusCircleTwoTone onClick={(e) => onExpand(record, e)} />
) : (
<PlusCircleTwoTone onClick={(e) => onExpand(record, e)} />
),
}}
/>
</Card>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(TechDispatchedParts);

View File

@@ -1,23 +1,26 @@
import { BackTop, Layout } from "antd"; import { BackTop, Layout } from "antd";
import React, { lazy, Suspense, useEffect } from "react"; import React, { Suspense, lazy, useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Redirect, Route, Switch } from "react-router-dom"; import { Redirect, Route, Switch } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import ErrorBoundary from "../../components/error-boundary/error-boundary.component"; import ErrorBoundary from "../../components/error-boundary/error-boundary.component";
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import TechHeader from "../../components/tech-header/tech-header.component"; import TechHeader from "../../components/tech-header/tech-header.component";
import TechSider from "../../components/tech-sider/tech-sider.component";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
import "./tech.page.styles.scss";
import TechLookupJobsDrawer from "../../components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component"; import TechLookupJobsDrawer from "../../components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component";
import TechSider from "../../components/tech-sider/tech-sider.component";
import UpdateAlert from "../../components/update-alert/update-alert.component"; import UpdateAlert from "../../components/update-alert/update-alert.component";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import "./tech.page.styles.scss";
import "./tech.page.styles.scss";
const TimeTicketModalContainer = lazy(() => const TimeTicketModalContainer = lazy(() =>
import("../../components/time-ticket-modal/time-ticket-modal.container") import("../../components/time-ticket-modal/time-ticket-modal.container")
); );
const EmailOverlayContainer = lazy(() =>
import("../../components/email-overlay/email-overlay.container.jsx")
);
const PrintCenterModalContainer = lazy(() => const PrintCenterModalContainer = lazy(() =>
import("../../components/print-center-modal/print-center-modal.container") import("../../components/print-center-modal/print-center-modal.container")
); );
@@ -45,6 +48,10 @@ const TimeTicketModalTask = lazy(() =>
const TechAssignedProdJobs = lazy(() => const TechAssignedProdJobs = lazy(() =>
import("../tech-assigned-prod-jobs/tech-assigned-prod-jobs.component") import("../tech-assigned-prod-jobs/tech-assigned-prod-jobs.component")
); );
const TechDispatchedParts = lazy(() =>
import("../tech-dispatched-parts/tech-dispatched-parts.page")
);
const { Content } = Layout; const { Content } = Layout;
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -78,6 +85,7 @@ export function TechPage({ technician, match }) {
> >
<FeatureWrapper featureName="tech-console"> <FeatureWrapper featureName="tech-console">
<TimeTicketModalContainer /> <TimeTicketModalContainer />
<EmailOverlayContainer />
<PrintCenterModalContainer /> <PrintCenterModalContainer />
<TimeTicketModalTask /> <TimeTicketModalTask />
<Switch> <Switch>
@@ -116,6 +124,11 @@ export function TechPage({ technician, match }) {
path={`${match.path}/assigned`} path={`${match.path}/assigned`}
component={TechAssignedProdJobs} component={TechAssignedProdJobs}
/> />
<Route
exact
path={`${match.path}/dispatchedparts`}
component={TechDispatchedParts}
/>
</Switch> </Switch>
</FeatureWrapper> </FeatureWrapper>
</Suspense> </Suspense>

View File

@@ -197,11 +197,13 @@ export function* signInSuccessSaga({ payload }) {
LogRocket.identify(payload.email); LogRocket.identify(payload.email);
try { try {
window.$zoho.salesiq.visitor.name(payload.displayName); // window.$crisp.push([
window.$zoho.salesiq.visitor.email(payload.email); // "set",
window.$zoho.salesiq.visitor.id(payload.email); // "user:nickname",
// [payload.displayName || payload.email],
// ]);
window.$zoho.salesiq.chat.waitime(60 * 30); //Wait up to 30 minutes for the chat response. // window.$crisp.push(["set", "session:segments", [["rome-user"]]]);
Sentry.setUser({ Sentry.setUser({
email: payload.email, email: payload.email,
@@ -286,9 +288,10 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
) )
); );
try { try {
window.$zoho.salesiq.visitor.info({ // window.$crisp.push(["set", "user:company", [payload.shopname]]);
Shop: payload.shopname, // if (authRecord[0] && authRecord[0].user.validemail) {
}); // window.$crisp.push(["set", "user:email", [authRecord[0].user.email]]);
// }
} catch (error) { } catch (error) {
console.error("Couldnt find $crisp."); console.error("Couldnt find $crisp.");
} }

View File

@@ -179,7 +179,7 @@
"is_credit_memo_short": "CM", "is_credit_memo_short": "CM",
"local_tax_rate": "Local Tax Rate", "local_tax_rate": "Local Tax Rate",
"ro_number": "RO Number", "ro_number": "RO Number",
"state_tax_rate": "Provincial/State Tax Rate", "state_tax_rate": "State Tax Rate",
"total": "Bill Total", "total": "Bill Total",
"vendor": "Vendor", "vendor": "Vendor",
"vendorname": "Vendor Name" "vendorname": "Vendor Name"
@@ -212,7 +212,7 @@
"onlycmforinvoiced": "Only credit memos can be entered for any job that has been invoiced, exported, or voided.", "onlycmforinvoiced": "Only credit memos can be entered for any job that has been invoiced, exported, or voided.",
"retailtotal": "Bills Retail Total", "retailtotal": "Bills Retail Total",
"savewithdiscrepancy": "You are about to save this bill with a discrepancy. The system will continue to use the calculated amount using the bill lines. Press cancel to return to the bill.", "savewithdiscrepancy": "You are about to save this bill with a discrepancy. The system will continue to use the calculated amount using the bill lines. Press cancel to return to the bill.",
"state_tax": "Provincial/State Tax", "state_tax": "State Tax",
"subtotal": "Subtotal", "subtotal": "Subtotal",
"totalreturns": "Total Returns" "totalreturns": "Total Returns"
}, },
@@ -224,6 +224,7 @@
"reexport": "Bill marked for re-export." "reexport": "Bill marked for re-export."
}, },
"validation": { "validation": {
"closingperiod": "This Bill Date is outside of the Closing Period.",
"inventoryquantity": "Quantity must be greater than or equal to what has been added to inventory ({{number}}).", "inventoryquantity": "Quantity must be greater than or equal to what has been added to inventory ({{number}}).",
"manualinhouse": "Manual posting to the in house vendor is restricted. ", "manualinhouse": "Manual posting to the in house vendor is restricted. ",
"unique_invoice_number": "This invoice number has already been entered for this vendor." "unique_invoice_number": "This invoice number has already been entered for this vendor."
@@ -260,9 +261,10 @@
"attach_pdf_to_email": "Attach PDF copy to sent emails?", "attach_pdf_to_email": "Attach PDF copy to sent emails?",
"bill_allow_post_to_closed": "Allow Bills to be posted to Closed Jobs", "bill_allow_post_to_closed": "Allow Bills to be posted to Closed Jobs",
"bill_federal_tax_rate": "Bills - Federal Tax Rate %", "bill_federal_tax_rate": "Bills - Federal Tax Rate %",
"bill_local_tax_rate": "Bill - Provincial/State Tax Rate %", "bill_local_tax_rate": "Bill - Local Tax Rate %",
"bill_state_tax_rate": "Bill - Provincial/State Tax Rate %", "bill_state_tax_rate": "Bill - State Tax Rate %",
"city": "City", "city": "City",
"closingperiod": "Closing Period",
"country": "Country", "country": "Country",
"dailybodytarget": "Scoreboard - Daily Body Target", "dailybodytarget": "Scoreboard - Daily Body Target",
"dailypainttarget": "Scoreboard - Daily Paint Target", "dailypainttarget": "Scoreboard - Daily Paint Target",
@@ -303,7 +305,7 @@
}, },
"invoice_federal_tax_rate": "Invoices - Federal Tax Rate", "invoice_federal_tax_rate": "Invoices - Federal Tax Rate",
"invoice_local_tax_rate": "Invoices - Local Tax Rate", "invoice_local_tax_rate": "Invoices - Local Tax Rate",
"invoice_state_tax_rate": "Invoices - Provincial/State Tax Rate", "invoice_state_tax_rate": "Invoices - State Tax Rate",
"jc_hourly_rates": { "jc_hourly_rates": {
"mapa": "Job Costing - Paint Materials Hourly Cost Rate", "mapa": "Job Costing - Paint Materials Hourly Cost Rate",
"mash": "Job Costing - Shop Materials Hourly Cost Rate" "mash": "Job Costing - Shop Materials Hourly Cost Rate"
@@ -471,6 +473,11 @@
"responsibilitycenter_accountname": "Account Name", "responsibilitycenter_accountname": "Account Name",
"responsibilitycenter_accountnumber": "Account Number", "responsibilitycenter_accountnumber": "Account Number",
"responsibilitycenter_rate": "Rate", "responsibilitycenter_rate": "Rate",
"responsibilitycenter_tax_rate": "Tax {{typeNum}} Tier {{typeNumIterator}} Rate",
"responsibilitycenter_tax_sur": "Tax {{typeNum}} Tier {{typeNumIterator}} Surcharge",
"responsibilitycenter_tax_thres": "Tax {{typeNum}} Tier {{typeNumIterator}} Threshold",
"responsibilitycenter_tax_tier": "Tax {{typeNum}} Tier {{typeNumIterator}}",
"responsibilitycenter_tax_type": "Tax {{typeNum}} Type",
"responsibilitycenters": { "responsibilitycenters": {
"ap": "Accounts Payable", "ap": "Accounts Payable",
"ar": "Accounts Receivable", "ar": "Accounts Receivable",
@@ -493,7 +500,7 @@
"lam": "Mechanical", "lam": "Mechanical",
"lar": "Refinish", "lar": "Refinish",
"las": "Structural", "las": "Structural",
"lau": "Detail", "lau": "User Defined",
"local_tax": "Local Tax", "local_tax": "Local Tax",
"mapa": "Paint Materials", "mapa": "Paint Materials",
"mash": "Shop Materials", "mash": "Shop Materials",
@@ -514,9 +521,9 @@
"description": "Description", "description": "Description",
"federal": "Federal Tax Applies", "federal": "Federal Tax Applies",
"local": "Local Tax Applies", "local": "Local Tax Applies",
"state": "Provincial/State Tax Applies" "state": "State Tax Applies"
}, },
"state_tax": "Provincial/State Tax", "state_tax": "State Tax",
"tow": "Towing" "tow": "Towing"
}, },
"schedule_end_time": "Schedule Ending Time", "schedule_end_time": "Schedule Ending Time",
@@ -539,7 +546,7 @@
"target": "Target (count)" "target": "Target (count)"
}, },
"state": "Province/State", "state": "Province/State",
"state_tax_id": "Provincial/State Tax ID (PST, QST)", "state_tax_id": "State Tax ID",
"status": "Status Label", "status": "Status Label",
"statuses": { "statuses": {
"active_statuses": "Active Statuses (Filtering for Active Jobs throughout system)", "active_statuses": "Active Statuses (Filtering for Active Jobs throughout system)",
@@ -570,7 +577,7 @@
"timezone": "Timezone", "timezone": "Timezone",
"tt_allow_post_to_invoiced": "Allow Time Tickets to be posted to Invoiced & Exported Jobs", "tt_allow_post_to_invoiced": "Allow Time Tickets to be posted to Invoiced & Exported Jobs",
"tt_enforce_hours_for_tech_console": "Restrict Claimable hours from Tech Console", "tt_enforce_hours_for_tech_console": "Restrict Claimable hours from Tech Console",
"use_fippa": "Use FIPPA for Names on Generated Documents?", "use_fippa": "Conceal Customer Information on Generated Documents?",
"use_paint_scale_data": "Use Paint Scale Data for Job Costing?", "use_paint_scale_data": "Use Paint Scale Data for Job Costing?",
"uselocalmediaserver": "Use Local Media Server?", "uselocalmediaserver": "Use Local Media Server?",
"website": "Website", "website": "Website",
@@ -611,8 +618,8 @@
"jobstatuses": "Job Statuses", "jobstatuses": "Job Statuses",
"laborrates": "Labor Rates", "laborrates": "Labor Rates",
"licensing": "Licensing", "licensing": "Licensing",
"md_tasks_presets": "Tasks Presets",
"md_parts_scan": "Parts Scan Rules", "md_parts_scan": "Parts Scan Rules",
"md_tasks_presets": "Tasks Presets",
"md_to_emails": "Preset To Emails", "md_to_emails": "Preset To Emails",
"md_to_emails_emails": "Emails", "md_to_emails_emails": "Emails",
"messagingpresets": "Messaging Presets", "messagingpresets": "Messaging Presets",
@@ -722,7 +729,7 @@
"refuelcharge": "Refuel Charge (per liter/gallon)", "refuelcharge": "Refuel Charge (per liter/gallon)",
"scheduledreturn": "Scheduled Return", "scheduledreturn": "Scheduled Return",
"start": "Contract Start", "start": "Contract Start",
"statetax": "Provincial/State Taxes", "statetax": "State Taxes",
"status": "Status" "status": "Status"
}, },
"labels": { "labels": {
@@ -1018,6 +1025,7 @@
"cancel": "Cancel", "cancel": "Cancel",
"clear": "Clear", "clear": "Clear",
"close": "Close", "close": "Close",
"copied": "Copied!",
"copylink": "Copy Link", "copylink": "Copy Link",
"create": "Create", "create": "Create",
"delete": "Delete", "delete": "Delete",
@@ -1034,6 +1042,7 @@
"saveandnew": "Save and New", "saveandnew": "Save and New",
"selectall": "Select All", "selectall": "Select All",
"send": "Send", "send": "Send",
"sendbysms": "Send by SMS",
"senderrortosupport": "Send Error to Support", "senderrortosupport": "Send Error to Support",
"submit": "Submit", "submit": "Submit",
"tryagain": "Try Again", "tryagain": "Try Again",
@@ -1095,6 +1104,7 @@
"passwordsdonotmatch": "The passwords you have entered do not match.", "passwordsdonotmatch": "The passwords you have entered do not match.",
"print": "Print", "print": "Print",
"refresh": "Refresh", "refresh": "Refresh",
"reports": "Reports",
"required": "Required", "required": "Required",
"saturday": "Saturday", "saturday": "Saturday",
"search": "Search...", "search": "Search...",
@@ -1194,24 +1204,26 @@
}, },
"job_payments": { "job_payments": {
"buttons": { "buttons": {
"goback": "Cancel", "goback": "Go Back",
"proceedtopayment": "Proceed to Payment", "proceedtopayment": "Proceed to Payment",
"refundpayment": "Refund Payment" "refundpayment": "Refund Payment"
}, },
"notifications": { "notifications": {
"error": { "error": {
"description": "Please try again. Make sure the refund amount does not exceeds the payment amount.", "description": "Please try again. Make sure the refund amount does not exceeds the payment amount.",
"title": "Error Refunding Payment" "openingip": "Error connecting to IntelliPay service.",
"title": "Error placing refund"
} }
}, },
"titles": { "titles": {
"amount": "Amount", "amount": "Amount",
"dateOfPayment": "Date", "dateOfPayment": "Date of Payment",
"descriptions": "Description", "descriptions": "Payment Details",
"payer": "Payer", "payer": "Payer",
"payername": "Payer Name", "payername": "Payer Name",
"paymentid": "Payment ID", "paymentid": "Payment Reference ID",
"paymenttype": "Type", "paymentnum": "Payment Number",
"paymenttype": "Payment Type",
"refundamount": "Refund Amount", "refundamount": "Refund Amount",
"transactionid": "Transaction ID" "transactionid": "Transaction ID"
} }
@@ -1361,6 +1373,8 @@
"sendpartspricechange": "Send Parts Price Change", "sendpartspricechange": "Send Parts Price Change",
"sendtodms": "Send to DMS", "sendtodms": "Send to DMS",
"sync": "Sync", "sync": "Sync",
"taxprofileoverride": "Override Tax Profile with Shop Configuration",
"taxprofileoverride_confirm": "Are you sure you want to override the tax profile information? This cannot be undone without re-importing the job. ",
"uninvoice": "Uninvoice", "uninvoice": "Uninvoice",
"unvoid": "Unvoid Job", "unvoid": "Unvoid Job",
"viewchecklist": "View Checklists", "viewchecklist": "View Checklists",
@@ -1432,6 +1446,26 @@
"ccf": "CC Refuel", "ccf": "CC Refuel",
"ccm": "CC Mileage", "ccm": "CC Mileage",
"cieca_id": "CIECA ID", "cieca_id": "CIECA ID",
"cieca_pfl": {
"lbr_tax_in": "Tax Labor Indicator",
"lbr_tx_in1": "Tax 1 Indicator",
"lbr_tx_in2": "Tax 2 Indicator",
"lbr_tx_in3": "Tax 3 Indicator",
"lbr_tx_in4": "Tax 4 Indicator",
"lbr_tx_in5": "Tax 5 Indicator"
},
"cieca_pfo": {
"stor_t_in1": "Storage Tax 1 Indicator",
"stor_t_in2": "Storage Tax 2 Indicator",
"stor_t_in3": "Storage Tax 3 Indicator",
"stor_t_in4": "Storage Tax 4 Indicator",
"stor_t_in5": "Storage Tax 5 Indicator",
"tow_t_in1": "Tow Tax 1 Indicator",
"tow_t_in2": "Tow Tax 2 Indicator",
"tow_t_in3": "Tow Tax 3 Indicator",
"tow_t_in4": "Tow Tax 4 Indicator",
"tow_t_in5": "Tow Tax 5 Indicator"
},
"claim_total": "Claim Total", "claim_total": "Claim Total",
"class": "Class", "class": "Class",
"clm_no": "Claim #", "clm_no": "Claim #",
@@ -1545,6 +1579,19 @@
"mapa": "Paint Materials", "mapa": "Paint Materials",
"mash": "Shop Materials", "mash": "Shop Materials",
"matd": "Tire Disposal", "matd": "Tire Disposal",
"materials": {
"MAPA": "Paint Materials",
"MASH": "Shop Materials",
"cal_maxdlr": "Threshhold",
"cal_opcode": "OP Codes",
"mat_tx_in1": "Tax 1 Indicator",
"mat_tx_in2": "Tax 2 Indicator",
"mat_tx_in3": "Tax 3 Indicator",
"mat_tx_in4": "Tax 4 Indicator",
"mat_tx_in5": "Tax 5 Indicator",
"materials": "Profile - Materials",
"tax_ind": "Tax Indicator"
},
"other_amount_payable": "Other Amount Payable", "other_amount_payable": "Other Amount Payable",
"owner": "Owner", "owner": "Owner",
"owner_owing": "Cust. Owes", "owner_owing": "Cust. Owes",
@@ -1567,6 +1614,11 @@
"prt_mkupp": "Markup %", "prt_mkupp": "Markup %",
"prt_tax_in": "Tax Indicator", "prt_tax_in": "Tax Indicator",
"prt_tax_rt": "Part Tax Rate", "prt_tax_rt": "Part Tax Rate",
"prt_tx_in1": "Tax 1 Indicator",
"prt_tx_in2": "Tax 2 Indicator",
"prt_tx_in3": "Tax 3 Indicator",
"prt_tx_in4": "Tax 4 Indicator",
"prt_tx_in5": "Tax 5 Indicator",
"prt_type": "Part Type" "prt_type": "Part Type"
}, },
"partsstatus": "Parts Status", "partsstatus": "Parts Status",
@@ -1623,7 +1675,7 @@
"servicing_dealer_contact": "Servicing Dealer Contact", "servicing_dealer_contact": "Servicing Dealer Contact",
"special_coverage_policy": "Special Coverage Policy", "special_coverage_policy": "Special Coverage Policy",
"specialcoveragepolicy": "Special Coverage Policy", "specialcoveragepolicy": "Special Coverage Policy",
"state_tax_rate": "Provincial/State Tax Rate", "state_tax_rate": "State Tax Rate",
"status": "Job Status", "status": "Job Status",
"storage_payable": "Storage", "storage_payable": "Storage",
"tax_lbr_rt": "Labor Tax Rate", "tax_lbr_rt": "Labor Tax Rate",
@@ -1694,8 +1746,12 @@
"checklistcompletedby": "Checklist completed by {{by}} at {{at}}", "checklistcompletedby": "Checklist completed by {{by}} at {{at}}",
"checklistdocuments": "Checklist Documents", "checklistdocuments": "Checklist Documents",
"checklists": "Checklists", "checklists": "Checklists",
"cieca_pfl": "Profile - Labor",
"cieca_pfo": "Profile - Other",
"cieca_pft": "Profile - Taxes",
"closeconfirm": "Are you sure you want to close this job? This cannot be easily undone.", "closeconfirm": "Are you sure you want to close this job? This cannot be easily undone.",
"closejob": "Close Job {{ro_number}}", "closejob": "Close Job {{ro_number}}",
"closingperiod": "This Invoice Date is outside of the Closing Period.",
"contracts": "CC Contracts", "contracts": "CC Contracts",
"convertedtolabor": "Lines Converted to Labor", "convertedtolabor": "Lines Converted to Labor",
"cost": "Cost", "cost": "Cost",
@@ -1765,6 +1821,10 @@
"mapa": "Paint Materials", "mapa": "Paint Materials",
"markforreexport": "Mark for Re-export", "markforreexport": "Mark for Re-export",
"mash": "Shop Materials", "mash": "Shop Materials",
"materials": {
"mapa": ""
},
"missingprofileinfo": "This job has missing tax profile info. To ensure correct totals calculations, re-import the job.",
"multipayers": "Additional Payers", "multipayers": "Additional Payers",
"net_repairs": "Net Repairs", "net_repairs": "Net Repairs",
"notes": "Notes", "notes": "Notes",
@@ -1791,7 +1851,7 @@
"totalreturns": "The total <b>retail</b> amount of returns created for this job." "totalreturns": "The total <b>retail</b> amount of returns created for this job."
}, },
"ppc": "This line contains a part price change.", "ppc": "This line contains a part price change.",
"profileadjustments": "Profile Disc./Mkup (Already included above)", "profileadjustments": "Profile Disc./Mkup",
"prt_dsmk_total": "Line Item Adjustment", "prt_dsmk_total": "Line Item Adjustment",
"rates": "Rates", "rates": "Rates",
"rates_subtotal": "All Rates Subtotal", "rates_subtotal": "All Rates Subtotal",
@@ -1819,7 +1879,7 @@
"savebeforeconversion": "You have unsaved changes on the job. Please save them before converting it. ", "savebeforeconversion": "You have unsaved changes on the job. Please save them before converting it. ",
"scheduledinchange": "The scheduled in is based off the latest appointment. To change this date, please schedule or reschedule the job. ", "scheduledinchange": "The scheduled in is based off the latest appointment. To change this date, please schedule or reschedule the job. ",
"specialcoveragepolicy": "Special Coverage Policy Applies", "specialcoveragepolicy": "Special Coverage Policy Applies",
"state_tax_amt": "Provincial/State Taxes", "state_tax_amt": "State Taxes",
"subletstotal": "Sublets Total", "subletstotal": "Sublets Total",
"subtotal": "Subtotal", "subtotal": "Subtotal",
"supplementnote": "The job had a supplement imported.", "supplementnote": "The job had a supplement imported.",
@@ -1931,7 +1991,7 @@
"customers": "Customers", "customers": "Customers",
"dashboard": "Dashboard", "dashboard": "Dashboard",
"enterbills": "Enter Bills", "enterbills": "Enter Bills",
"entercardpayment": "Enter Card Payments", "entercardpayment": "New Card Charge",
"enterpayment": "Enter Payments", "enterpayment": "Enter Payments",
"entertimeticket": "Enter Time Tickets", "entertimeticket": "Enter Time Tickets",
"export": "Export", "export": "Export",
@@ -1943,7 +2003,6 @@
"newjob": "Create New Job", "newjob": "Create New Job",
"owners": "Owners", "owners": "Owners",
"parts-queue": "Parts Queue", "parts-queue": "Parts Queue",
"paymentremindersms": "Send Payment Reminder via SMS",
"phonebook": "Phonebook", "phonebook": "Phonebook",
"productionboard": "Production Board - Visual", "productionboard": "Production Board - Visual",
"productionlist": "Production Board - List", "productionlist": "Production Board - List",
@@ -2001,6 +2060,7 @@
"tech": { "tech": {
"assignedjobs": "Assigned Jobs", "assignedjobs": "Assigned Jobs",
"claimtask": "Flag Hours", "claimtask": "Flag Hours",
"dispatchedparts": "Dispatched Parts",
"home": "Home", "home": "Home",
"jobclockin": "Job Clock In", "jobclockin": "Job Clock In",
"jobclockout": "Job Clock Out", "jobclockout": "Job Clock Out",
@@ -2142,7 +2202,11 @@
} }
}, },
"parts_dispatch": { "parts_dispatch": {
"actions": {
"accept": "Accept"
},
"errors": { "errors": {
"accepting": "Error accepting parts dispatch. {{error}}",
"creating": "Error dispatching parts. {{error}}" "creating": "Error dispatching parts. {{error}}"
}, },
"fields": { "fields": {
@@ -2224,9 +2288,13 @@
} }
}, },
"payments": { "payments": {
"actions": {
"generatepaymentlink": "Generate Payment Link"
},
"errors": { "errors": {
"exporting": "Error exporting payment(s). {{error}}", "exporting": "Error exporting payment(s). {{error}}",
"exporting-partner": "Error exporting to partner. Please check the partner interaction log for more errors." "exporting-partner": "Error exporting to partner. Please check the partner interaction log for more errors.",
"inserting": "Error inserting payment. {{error}}"
}, },
"fields": { "fields": {
"amount": "Amount", "amount": "Amount",
@@ -2253,6 +2321,7 @@
"markforreexport": "Mark for Re-export", "markforreexport": "Mark for Re-export",
"new": "New Payment", "new": "New Payment",
"signup": "Please contact support to sign up for electronic payments.", "signup": "Please contact support to sign up for electronic payments.",
"smspaymentreminder": "This is {{shopname}} reminding you about your balance of {{amount}}. To pay, click the following link {{payment_link}}.",
"title": "Payments", "title": "Payments",
"totalpayments": "Total Payments" "totalpayments": "Total Payments"
}, },
@@ -2768,6 +2837,7 @@
"committed": "Committed", "committed": "Committed",
"committed_at": "Committed At", "committed_at": "Committed At",
"cost_center": "Cost Center", "cost_center": "Cost Center",
"created_by": "Created By",
"date": "Ticket Date", "date": "Ticket Date",
"efficiency": "Efficiency", "efficiency": "Efficiency",
"employee": "Employee", "employee": "Employee",

View File

@@ -224,6 +224,7 @@
"reexport": "" "reexport": ""
}, },
"validation": { "validation": {
"closingperiod": "",
"inventoryquantity": "", "inventoryquantity": "",
"manualinhouse": "", "manualinhouse": "",
"unique_invoice_number": "" "unique_invoice_number": ""
@@ -263,6 +264,7 @@
"bill_local_tax_rate": "", "bill_local_tax_rate": "",
"bill_state_tax_rate": "", "bill_state_tax_rate": "",
"city": "", "city": "",
"closingperiod": "",
"country": "", "country": "",
"dailybodytarget": "", "dailybodytarget": "",
"dailypainttarget": "", "dailypainttarget": "",
@@ -471,6 +473,11 @@
"responsibilitycenter_accountname": "", "responsibilitycenter_accountname": "",
"responsibilitycenter_accountnumber": "", "responsibilitycenter_accountnumber": "",
"responsibilitycenter_rate": "", "responsibilitycenter_rate": "",
"responsibilitycenter_tax_rate": "",
"responsibilitycenter_tax_sur": "",
"responsibilitycenter_tax_thres": "",
"responsibilitycenter_tax_tier": "",
"responsibilitycenter_tax_type": "",
"responsibilitycenters": { "responsibilitycenters": {
"ap": "", "ap": "",
"ar": "", "ar": "",
@@ -1018,6 +1025,7 @@
"cancel": "", "cancel": "",
"clear": "", "clear": "",
"close": "", "close": "",
"copied": "",
"copylink": "", "copylink": "",
"create": "", "create": "",
"delete": "Borrar", "delete": "Borrar",
@@ -1034,6 +1042,7 @@
"saveandnew": "", "saveandnew": "",
"selectall": "", "selectall": "",
"send": "", "send": "",
"sendbysms": "",
"senderrortosupport": "", "senderrortosupport": "",
"submit": "", "submit": "",
"tryagain": "", "tryagain": "",
@@ -1095,6 +1104,7 @@
"passwordsdonotmatch": "", "passwordsdonotmatch": "",
"print": "", "print": "",
"refresh": "", "refresh": "",
"reports": "",
"required": "", "required": "",
"saturday": "", "saturday": "",
"search": "Buscar...", "search": "Buscar...",
@@ -1201,6 +1211,7 @@
"notifications": { "notifications": {
"error": { "error": {
"description": "", "description": "",
"openingip": "",
"title": "" "title": ""
} }
}, },
@@ -1211,6 +1222,7 @@
"payer": "", "payer": "",
"payername": "", "payername": "",
"paymentid": "", "paymentid": "",
"paymentnum": "",
"paymenttype": "", "paymenttype": "",
"refundamount": "", "refundamount": "",
"transactionid": "" "transactionid": ""
@@ -1361,6 +1373,8 @@
"sendpartspricechange": "", "sendpartspricechange": "",
"sendtodms": "", "sendtodms": "",
"sync": "", "sync": "",
"taxprofileoverride": "",
"taxprofileoverride_confirm": "",
"uninvoice": "", "uninvoice": "",
"unvoid": "", "unvoid": "",
"viewchecklist": "", "viewchecklist": "",
@@ -1432,6 +1446,26 @@
"ccf": "", "ccf": "",
"ccm": "", "ccm": "",
"cieca_id": "CIECA ID", "cieca_id": "CIECA ID",
"cieca_pfl": {
"lbr_tax_in": "",
"lbr_tx_in1": "",
"lbr_tx_in2": "",
"lbr_tx_in3": "",
"lbr_tx_in4": "",
"lbr_tx_in5": ""
},
"cieca_pfo": {
"stor_t_in1": "",
"stor_t_in2": "",
"stor_t_in3": "",
"stor_t_in4": "",
"stor_t_in5": "",
"tow_t_in1": "",
"tow_t_in2": "",
"tow_t_in3": "",
"tow_t_in4": "",
"tow_t_in5": ""
},
"claim_total": "Reclamar total", "claim_total": "Reclamar total",
"class": "", "class": "",
"clm_no": "Reclamación #", "clm_no": "Reclamación #",
@@ -1545,6 +1579,19 @@
"mapa": "", "mapa": "",
"mash": "", "mash": "",
"matd": "", "matd": "",
"materials": {
"MAPA": "",
"MASH": "",
"cal_maxdlr": "",
"cal_opcode": "",
"mat_tx_in1": "",
"mat_tx_in2": "",
"mat_tx_in3": "",
"mat_tx_in4": "",
"mat_tx_in5": "",
"materials": "",
"tax_ind": ""
},
"other_amount_payable": "Otra cantidad a pagar", "other_amount_payable": "Otra cantidad a pagar",
"owner": "Propietario", "owner": "Propietario",
"owner_owing": "Cust. Debe", "owner_owing": "Cust. Debe",
@@ -1567,6 +1614,11 @@
"prt_mkupp": "", "prt_mkupp": "",
"prt_tax_in": "", "prt_tax_in": "",
"prt_tax_rt": "", "prt_tax_rt": "",
"prt_tx_in1": "",
"prt_tx_in2": "",
"prt_tx_in3": "",
"prt_tx_in4": "",
"prt_tx_in5": "",
"prt_type": "" "prt_type": ""
}, },
"partsstatus": "", "partsstatus": "",
@@ -1694,8 +1746,12 @@
"checklistcompletedby": "", "checklistcompletedby": "",
"checklistdocuments": "", "checklistdocuments": "",
"checklists": "", "checklists": "",
"cieca_pfl": "",
"cieca_pfo": "",
"cieca_pft": "",
"closeconfirm": "", "closeconfirm": "",
"closejob": "", "closejob": "",
"closingperiod": "",
"contracts": "", "contracts": "",
"convertedtolabor": "", "convertedtolabor": "",
"cost": "", "cost": "",
@@ -1765,6 +1821,10 @@
"mapa": "", "mapa": "",
"markforreexport": "", "markforreexport": "",
"mash": "", "mash": "",
"materials": {
"mapa": ""
},
"missingprofileinfo": "",
"multipayers": "", "multipayers": "",
"net_repairs": "", "net_repairs": "",
"notes": "Notas", "notes": "Notas",
@@ -1943,7 +2003,6 @@
"newjob": "", "newjob": "",
"owners": "propietarios", "owners": "propietarios",
"parts-queue": "", "parts-queue": "",
"paymentremindersms": "",
"phonebook": "", "phonebook": "",
"productionboard": "", "productionboard": "",
"productionlist": "", "productionlist": "",
@@ -2001,6 +2060,7 @@
"tech": { "tech": {
"assignedjobs": "", "assignedjobs": "",
"claimtask": "", "claimtask": "",
"dispatchedparts": "",
"home": "", "home": "",
"jobclockin": "", "jobclockin": "",
"jobclockout": "", "jobclockout": "",
@@ -2142,7 +2202,11 @@
} }
}, },
"parts_dispatch": { "parts_dispatch": {
"actions": {
"accept": ""
},
"errors": { "errors": {
"accepting": "",
"creating": "" "creating": ""
}, },
"fields": { "fields": {
@@ -2224,9 +2288,13 @@
} }
}, },
"payments": { "payments": {
"actions": {
"generatepaymentlink": ""
},
"errors": { "errors": {
"exporting": "", "exporting": "",
"exporting-partner": "" "exporting-partner": "",
"inserting": ""
}, },
"fields": { "fields": {
"amount": "", "amount": "",
@@ -2253,6 +2321,7 @@
"markforreexport": "", "markforreexport": "",
"new": "", "new": "",
"signup": "", "signup": "",
"smspaymentreminder": "",
"title": "", "title": "",
"totalpayments": "" "totalpayments": ""
}, },
@@ -2768,6 +2837,7 @@
"committed": "", "committed": "",
"committed_at": "", "committed_at": "",
"cost_center": "", "cost_center": "",
"created_by": "",
"date": "", "date": "",
"efficiency": "", "efficiency": "",
"employee": "", "employee": "",

View File

@@ -224,6 +224,7 @@
"reexport": "" "reexport": ""
}, },
"validation": { "validation": {
"closingperiod": "",
"inventoryquantity": "", "inventoryquantity": "",
"manualinhouse": "", "manualinhouse": "",
"unique_invoice_number": "" "unique_invoice_number": ""
@@ -263,6 +264,7 @@
"bill_local_tax_rate": "", "bill_local_tax_rate": "",
"bill_state_tax_rate": "", "bill_state_tax_rate": "",
"city": "", "city": "",
"closingperiod": "",
"country": "", "country": "",
"dailybodytarget": "", "dailybodytarget": "",
"dailypainttarget": "", "dailypainttarget": "",
@@ -471,6 +473,11 @@
"responsibilitycenter_accountname": "", "responsibilitycenter_accountname": "",
"responsibilitycenter_accountnumber": "", "responsibilitycenter_accountnumber": "",
"responsibilitycenter_rate": "", "responsibilitycenter_rate": "",
"responsibilitycenter_tax_rate": "",
"responsibilitycenter_tax_sur": "",
"responsibilitycenter_tax_thres": "",
"responsibilitycenter_tax_tier": "",
"responsibilitycenter_tax_type": "",
"responsibilitycenters": { "responsibilitycenters": {
"ap": "", "ap": "",
"ar": "", "ar": "",
@@ -1018,6 +1025,7 @@
"cancel": "", "cancel": "",
"clear": "", "clear": "",
"close": "", "close": "",
"copied": "",
"copylink": "", "copylink": "",
"create": "", "create": "",
"delete": "Effacer", "delete": "Effacer",
@@ -1034,6 +1042,7 @@
"saveandnew": "", "saveandnew": "",
"selectall": "", "selectall": "",
"send": "", "send": "",
"sendbysms": "",
"senderrortosupport": "", "senderrortosupport": "",
"submit": "", "submit": "",
"tryagain": "", "tryagain": "",
@@ -1095,6 +1104,7 @@
"passwordsdonotmatch": "", "passwordsdonotmatch": "",
"print": "", "print": "",
"refresh": "", "refresh": "",
"reports": "",
"required": "", "required": "",
"saturday": "", "saturday": "",
"search": "Chercher...", "search": "Chercher...",
@@ -1201,6 +1211,7 @@
"notifications": { "notifications": {
"error": { "error": {
"description": "", "description": "",
"openingip": "",
"title": "" "title": ""
} }
}, },
@@ -1211,6 +1222,7 @@
"payer": "", "payer": "",
"payername": "", "payername": "",
"paymentid": "", "paymentid": "",
"paymentnum": "",
"paymenttype": "", "paymenttype": "",
"refundamount": "", "refundamount": "",
"transactionid": "" "transactionid": ""
@@ -1361,6 +1373,8 @@
"sendpartspricechange": "", "sendpartspricechange": "",
"sendtodms": "", "sendtodms": "",
"sync": "", "sync": "",
"taxprofileoverride": "",
"taxprofileoverride_confirm": "",
"uninvoice": "", "uninvoice": "",
"unvoid": "", "unvoid": "",
"viewchecklist": "", "viewchecklist": "",
@@ -1432,6 +1446,26 @@
"ccf": "", "ccf": "",
"ccm": "", "ccm": "",
"cieca_id": "CIECA ID", "cieca_id": "CIECA ID",
"cieca_pfl": {
"lbr_tax_in": "",
"lbr_tx_in1": "",
"lbr_tx_in2": "",
"lbr_tx_in3": "",
"lbr_tx_in4": "",
"lbr_tx_in5": ""
},
"cieca_pfo": {
"stor_t_in1": "",
"stor_t_in2": "",
"stor_t_in3": "",
"stor_t_in4": "",
"stor_t_in5": "",
"tow_t_in1": "",
"tow_t_in2": "",
"tow_t_in3": "",
"tow_t_in4": "",
"tow_t_in5": ""
},
"claim_total": "Total réclamation", "claim_total": "Total réclamation",
"class": "", "class": "",
"clm_no": "Prétendre #", "clm_no": "Prétendre #",
@@ -1545,6 +1579,19 @@
"mapa": "", "mapa": "",
"mash": "", "mash": "",
"matd": "", "matd": "",
"materials": {
"MAPA": "",
"MASH": "",
"cal_maxdlr": "",
"cal_opcode": "",
"mat_tx_in1": "",
"mat_tx_in2": "",
"mat_tx_in3": "",
"mat_tx_in4": "",
"mat_tx_in5": "",
"materials": "",
"tax_ind": ""
},
"other_amount_payable": "Autre montant à payer", "other_amount_payable": "Autre montant à payer",
"owner": "Propriétaire", "owner": "Propriétaire",
"owner_owing": "Cust. Owes", "owner_owing": "Cust. Owes",
@@ -1567,6 +1614,11 @@
"prt_mkupp": "", "prt_mkupp": "",
"prt_tax_in": "", "prt_tax_in": "",
"prt_tax_rt": "", "prt_tax_rt": "",
"prt_tx_in1": "",
"prt_tx_in2": "",
"prt_tx_in3": "",
"prt_tx_in4": "",
"prt_tx_in5": "",
"prt_type": "" "prt_type": ""
}, },
"partsstatus": "", "partsstatus": "",
@@ -1694,8 +1746,12 @@
"checklistcompletedby": "", "checklistcompletedby": "",
"checklistdocuments": "", "checklistdocuments": "",
"checklists": "", "checklists": "",
"cieca_pfl": "",
"cieca_pfo": "",
"cieca_pft": "",
"closeconfirm": "", "closeconfirm": "",
"closejob": "", "closejob": "",
"closingperiod": "",
"contracts": "", "contracts": "",
"convertedtolabor": "", "convertedtolabor": "",
"cost": "", "cost": "",
@@ -1765,6 +1821,10 @@
"mapa": "", "mapa": "",
"markforreexport": "", "markforreexport": "",
"mash": "", "mash": "",
"materials": {
"mapa": ""
},
"missingprofileinfo": "",
"multipayers": "", "multipayers": "",
"net_repairs": "", "net_repairs": "",
"notes": "Remarques", "notes": "Remarques",
@@ -1943,7 +2003,6 @@
"newjob": "", "newjob": "",
"owners": "Propriétaires", "owners": "Propriétaires",
"parts-queue": "", "parts-queue": "",
"paymentremindersms": "",
"phonebook": "", "phonebook": "",
"productionboard": "", "productionboard": "",
"productionlist": "", "productionlist": "",
@@ -2001,6 +2060,7 @@
"tech": { "tech": {
"assignedjobs": "", "assignedjobs": "",
"claimtask": "", "claimtask": "",
"dispatchedparts": "",
"home": "", "home": "",
"jobclockin": "", "jobclockin": "",
"jobclockout": "", "jobclockout": "",
@@ -2142,7 +2202,11 @@
} }
}, },
"parts_dispatch": { "parts_dispatch": {
"actions": {
"accept": ""
},
"errors": { "errors": {
"accepting": "",
"creating": "" "creating": ""
}, },
"fields": { "fields": {
@@ -2224,9 +2288,13 @@
} }
}, },
"payments": { "payments": {
"actions": {
"generatepaymentlink": ""
},
"errors": { "errors": {
"exporting": "", "exporting": "",
"exporting-partner": "" "exporting-partner": "",
"inserting": ""
}, },
"fields": { "fields": {
"amount": "", "amount": "",
@@ -2253,6 +2321,7 @@
"markforreexport": "", "markforreexport": "",
"new": "", "new": "",
"signup": "", "signup": "",
"smspaymentreminder": "",
"title": "", "title": "",
"totalpayments": "" "totalpayments": ""
}, },
@@ -2768,6 +2837,7 @@
"committed": "", "committed": "",
"committed_at": "", "committed_at": "",
"cost_center": "", "cost_center": "",
"created_by": "",
"date": "", "date": "",
"efficiency": "", "efficiency": "",
"employee": "", "employee": "",

View File

@@ -1129,7 +1129,7 @@ export const TemplateList = (type, context) => {
disabled: false, disabled: false,
rangeFilter: { rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"), object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_open"), field: i18n.t("jobs.fields.date_invoiced"),
}, },
group: "jobs", group: "jobs",
}, },
@@ -1640,6 +1640,19 @@ export const TemplateList = (type, context) => {
}, },
group: "payroll", group: "payroll",
}, },
work_in_progress_jobs_excel: {
title: i18n.t("reportcenter.templates.work_in_progress_jobs"),
subject: i18n.t("reportcenter.templates.work_in_progress_jobs"),
key: "work_in_progress_jobs_excel",
//idtype: "vendor",
reporttype: "excel",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_open"),
},
group: "jobs",
},
work_in_progress_labour: { work_in_progress_labour: {
title: i18n.t("reportcenter.templates.work_in_progress_labour"), title: i18n.t("reportcenter.templates.work_in_progress_labour"),
description: "", description: "",

View File

@@ -3273,6 +3273,9 @@
- ca_gst_registrant - ca_gst_registrant
- cat_no - cat_no
- category - category
- cieca_pfl
- cieca_pfo
- cieca_pft
- cieca_stl - cieca_stl
- cieca_ttl - cieca_ttl
- ciecaid - ciecaid
@@ -3314,6 +3317,7 @@
- date_repairstarted - date_repairstarted
- date_scheduled - date_scheduled
- date_towin - date_towin
- date_void
- ded_amt - ded_amt
- ded_note - ded_note
- ded_status - ded_status
@@ -3495,7 +3499,6 @@
- v_model_yr - v_model_yr
- v_vin - v_vin
- vehicleid - vehicleid
- date_void
- voided - voided
select_permissions: select_permissions:
- role: user - role: user
@@ -3539,6 +3542,9 @@
- ca_gst_registrant - ca_gst_registrant
- cat_no - cat_no
- category - category
- cieca_pfl
- cieca_pfo
- cieca_pft
- cieca_stl - cieca_stl
- cieca_ttl - cieca_ttl
- ciecaid - ciecaid
@@ -3580,6 +3586,7 @@
- date_repairstarted - date_repairstarted
- date_scheduled - date_scheduled
- date_towin - date_towin
- date_void
- ded_amt - ded_amt
- ded_note - ded_note
- ded_status - ded_status
@@ -3762,7 +3769,6 @@
- v_model_yr - v_model_yr
- v_vin - v_vin
- vehicleid - vehicleid
- date_void
- voided - voided
filter: filter:
bodyshop: bodyshop:
@@ -3816,6 +3822,9 @@
- ca_gst_registrant - ca_gst_registrant
- cat_no - cat_no
- category - category
- cieca_pfl
- cieca_pfo
- cieca_pft
- cieca_stl - cieca_stl
- cieca_ttl - cieca_ttl
- ciecaid - ciecaid
@@ -3857,6 +3866,7 @@
- date_repairstarted - date_repairstarted
- date_scheduled - date_scheduled
- date_towin - date_towin
- date_void
- ded_amt - ded_amt
- ded_note - ded_note
- ded_status - ded_status
@@ -4039,7 +4049,6 @@
- v_model_yr - v_model_yr
- v_vin - v_vin
- vehicleid - vehicleid
- date_void
- voided - voided
filter: filter:
bodyshop: bodyshop:
@@ -4649,6 +4658,7 @@
_eq: X-Hasura-User-Id _eq: X-Hasura-User-Id
- active: - active:
_eq: true _eq: true
allow_aggregations: true
update_permissions: update_permissions:
- role: user - role: user
permission: permission:
@@ -4726,6 +4736,7 @@
_eq: X-Hasura-User-Id _eq: X-Hasura-User-Id
- active: - active:
_eq: true _eq: true
allow_aggregations: true
update_permissions: update_permissions:
- role: user - role: user
permission: permission:
@@ -5554,6 +5565,7 @@
- committed_at - committed_at
- cost_center - cost_center
- created_at - created_at
- created_by
- date - date
- employeeid - employeeid
- flat_rate - flat_rate
@@ -5578,6 +5590,7 @@
- committed_at - committed_at
- cost_center - cost_center
- created_at - created_at
- created_by
- date - date
- employeeid - employeeid
- flat_rate - flat_rate
@@ -5611,6 +5624,7 @@
- committed_at - committed_at
- cost_center - cost_center
- created_at - created_at
- created_by
- date - date
- employeeid - employeeid
- flat_rate - flat_rate

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "public"."parts_dispatch_employeeid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "parts_dispatch_employeeid" on
"public"."parts_dispatch" using btree ("employeeid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "public"."parts_dispatch_dispatchid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "parts_dispatch_dispatchid" on
"public"."parts_dispatch_lines" using btree ("partsdispatchid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "public"."parts_dispatch_line_accepted_at";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "parts_dispatch_line_accepted_at" on
"public"."parts_dispatch_lines" using btree ("accepted_at");

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."timetickets" add column "created_by" text
-- null;

View File

@@ -0,0 +1,2 @@
alter table "public"."timetickets" add column "created_by" text
null;

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,2 @@
alter table "public"."bodyshops" add column "claimscorpid" text
null;

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