Compare commits
104 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6b825ffdf | ||
|
|
1620b94a7b | ||
|
|
81f94eac6c | ||
|
|
ec7509670d | ||
|
|
0acfd3c4b1 | ||
|
|
bfc4cb1ad9 | ||
|
|
1f42be2e54 | ||
|
|
30d344af6b | ||
|
|
3ca989fd8c | ||
|
|
ce2086a480 | ||
|
|
63f7106d2b | ||
|
|
e75d8d1874 | ||
|
|
07bf84ed69 | ||
|
|
c2cc7b1e9e | ||
|
|
fe55eccbf9 | ||
|
|
88a71dd647 | ||
|
|
c3b395c99e | ||
|
|
33d5d9b462 | ||
|
|
b5a371d0cf | ||
|
|
3b35f38ad5 | ||
|
|
1f5c1b9658 | ||
|
|
37196e65c3 | ||
|
|
b20c605c85 | ||
|
|
d8ac708536 | ||
|
|
8a32fe50f3 | ||
|
|
7897a490bd | ||
|
|
17d73fc6d7 | ||
|
|
7d1910086e | ||
|
|
817c41afb9 | ||
|
|
04315a9045 | ||
|
|
d0871ffe21 | ||
|
|
dca587d6e0 | ||
|
|
19ec4cb021 | ||
|
|
346e82bdbc | ||
|
|
fd9575e5a5 | ||
|
|
af6bc0fb5a | ||
|
|
d0e289757f | ||
|
|
becc54f7b3 | ||
|
|
e3fabdac91 | ||
|
|
40621db556 | ||
|
|
c2e64a124d | ||
|
|
1fa83d124d | ||
|
|
cc734d3981 | ||
|
|
b41d69593d | ||
|
|
b7055aac84 | ||
|
|
09e505399c | ||
|
|
42f9f275c7 | ||
|
|
39b119b2e8 | ||
|
|
5bc224206c | ||
|
|
190da863c2 | ||
|
|
2711b5fce5 | ||
|
|
8e9358cd6f | ||
|
|
0e31bbb789 | ||
|
|
3b635aeed3 | ||
|
|
1f896b1ede | ||
|
|
b1ca09bd4f | ||
|
|
f9ca36ec89 | ||
|
|
9d479d4b4d | ||
|
|
23b5b740cb | ||
|
|
8f9b05b974 | ||
|
|
d1132e7d45 | ||
|
|
960b0b4d09 | ||
|
|
f35ea026b8 | ||
|
|
b1fc2828c8 | ||
|
|
bc25c23982 | ||
|
|
2215c8439e | ||
|
|
f98c9e6f71 | ||
|
|
28f2e8ad30 | ||
|
|
e67bc0d953 | ||
|
|
3adf6b649b | ||
|
|
f8243aa2b3 | ||
|
|
3c3f50d138 | ||
|
|
4f7e1b81ac | ||
|
|
806bdc4c70 | ||
|
|
a0572a0cec | ||
|
|
04cdf13e86 | ||
|
|
50349e91dc | ||
|
|
9998a8f154 | ||
|
|
9dcbcb2a43 | ||
|
|
5773f7a0f3 | ||
|
|
8614d88e71 | ||
|
|
fa5e26c52a | ||
|
|
90e1cbd390 | ||
|
|
947a3c6a88 | ||
|
|
a33662e6f0 | ||
|
|
6d1f04369e | ||
|
|
4a27726ef3 | ||
|
|
eedba97237 | ||
|
|
bcf095ed4f | ||
|
|
c3f9e268c7 | ||
|
|
c8442f0750 | ||
|
|
da5b446c30 | ||
|
|
e13b2bb969 | ||
|
|
e8969c4698 | ||
|
|
346d32a2bb | ||
|
|
6ba00a90be | ||
|
|
4293d20313 | ||
|
|
706c70c509 | ||
|
|
e872b1bf0a | ||
|
|
379fa060d8 | ||
|
|
a6258c6456 | ||
|
|
d6bf0a225b | ||
|
|
ec2b914e5e | ||
|
|
309a20148a |
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
import { useMutation, useQuery } from "@apollo/client";
|
import { useMutation, useQuery } from "@apollo/client";
|
||||||
import { Button, Form, PageHeader, Popconfirm, Space } from "antd";
|
import { Button, Divider, Form, PageHeader, Popconfirm, Space } from "antd";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
@@ -208,7 +208,7 @@ export function BillDetailEditcontainer({
|
|||||||
layout="vertical"
|
layout="vertical"
|
||||||
>
|
>
|
||||||
<BillFormContainer form={form} billEdit disabled={exported} />
|
<BillFormContainer form={form} billEdit disabled={exported} />
|
||||||
|
<Divider orientation="left">{t("general.labels.media")}</Divider>
|
||||||
{bodyshop.uselocalmediaserver ? (
|
{bodyshop.uselocalmediaserver ? (
|
||||||
<JobsDocumentsLocalGallery
|
<JobsDocumentsLocalGallery
|
||||||
job={{ id: data ? data.bills_by_pk.jobid : null }}
|
job={{ id: data ? data.bills_by_pk.jobid : null }}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export function BillDetailEditReturn({
|
|||||||
actions: {},
|
actions: {},
|
||||||
context: {
|
context: {
|
||||||
jobId: data.bills_by_pk.jobid,
|
jobId: data.bills_by_pk.jobid,
|
||||||
|
job: data.bills_by_pk.job,
|
||||||
vendorId: data.bills_by_pk.vendorid,
|
vendorId: data.bills_by_pk.vendorid,
|
||||||
returnFromBill: data.bills_by_pk.id,
|
returnFromBill: data.bills_by_pk.id,
|
||||||
invoiceNumber: data.bills_by_pk.invoice_number,
|
invoiceNumber: data.bills_by_pk.invoice_number,
|
||||||
@@ -173,7 +174,11 @@ export function BillDetailEditReturn({
|
|||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
<Button
|
<Button
|
||||||
disabled={data.bills_by_pk.is_credit_memo || disabled}
|
disabled={
|
||||||
|
data.bills_by_pk.is_credit_memo ||
|
||||||
|
data.bills_by_pk.isinhouse ||
|
||||||
|
disabled
|
||||||
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -504,10 +504,11 @@ export function BillFormComponent({
|
|||||||
billEdit={billEdit}
|
billEdit={billEdit}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<Divider orientation="left" style={{ display: billEdit ? "none" : null }}>
|
||||||
|
{t("documents.labels.upload")}
|
||||||
|
</Divider>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="upload"
|
name="upload"
|
||||||
label="Upload"
|
|
||||||
style={{ display: billEdit ? "none" : null }}
|
style={{ display: billEdit ? "none" : null }}
|
||||||
valuePropName="fileList"
|
valuePropName="fileList"
|
||||||
getValueFromEvent={(e) => {
|
getValueFromEvent={(e) => {
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export function BillsListTableComponent({
|
|||||||
)}
|
)}
|
||||||
<BillDeleteButton bill={record} jobid={job.id} />
|
<BillDeleteButton bill={record} jobid={job.id} />
|
||||||
<BillDetailEditReturnComponent
|
<BillDetailEditReturnComponent
|
||||||
data={{ bills_by_pk: { ...record, jobid: job.id } }}
|
data={{ bills_by_pk: { ...record, jobid: job.id, job: job } }}
|
||||||
disabled={
|
disabled={
|
||||||
record.is_credit_memo ||
|
record.is_credit_memo ||
|
||||||
record.vendorid === bodyshop.inhousevendorid ||
|
record.vendorid === bodyshop.inhousevendorid ||
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { useState } from "react";
|
|
||||||
import { Button, Form, InputNumber, Popover } from "antd";
|
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { CalculatorFilled } from "@ant-design/icons";
|
import { CalculatorFilled } from "@ant-design/icons";
|
||||||
|
import { Button, Form, InputNumber, Popover, Space } from "antd";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
export default function CABCpvrtCalculator({ disabled, form }) {
|
export default function CABCpvrtCalculator({ disabled, form }) {
|
||||||
const [visibility, setVisibility] = useState(false);
|
const [visibility, setVisibility] = useState(false);
|
||||||
|
|
||||||
@@ -26,10 +26,14 @@ export default function CABCpvrtCalculator({ disabled, form }) {
|
|||||||
<Form.Item name="days" label={t("jobs.labels.ca_bc_pvrt.days")}>
|
<Form.Item name="days" label={t("jobs.labels.ca_bc_pvrt.days")}>
|
||||||
<InputNumber precision={0} min={0} />
|
<InputNumber precision={0} min={0} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Button type="primary" htmlType="submit">
|
<div style={{ display: "flex", justifyContent: "flex-end" }}>
|
||||||
{t("general.actions.calculate")}
|
<Space>
|
||||||
</Button>
|
<Button type="primary" htmlType="submit">
|
||||||
<Button onClick={() => setVisibility(false)}>Close</Button>
|
{t("general.actions.calculate")}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => setVisibility(false)}>Close</Button>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
notification,
|
notification,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import moment from "moment";
|
|
||||||
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";
|
||||||
@@ -22,7 +21,6 @@ import {
|
|||||||
INSERT_PAYMENT_RESPONSE,
|
INSERT_PAYMENT_RESPONSE,
|
||||||
QUERY_RO_AND_OWNER_BY_JOB_PKS,
|
QUERY_RO_AND_OWNER_BY_JOB_PKS,
|
||||||
} from "../../graphql/payment_response.queries";
|
} from "../../graphql/payment_response.queries";
|
||||||
import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries";
|
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||||
import { selectCardPayment } from "../../redux/modals/modals.selectors";
|
import { selectCardPayment } from "../../redux/modals/modals.selectors";
|
||||||
@@ -48,12 +46,12 @@ const CardPaymentModalComponent = ({
|
|||||||
toggleModalVisible,
|
toggleModalVisible,
|
||||||
insertAuditTrail,
|
insertAuditTrail,
|
||||||
}) => {
|
}) => {
|
||||||
const { context } = cardPaymentModal;
|
const { context, actions } = cardPaymentModal;
|
||||||
|
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
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();
|
||||||
|
|
||||||
@@ -65,7 +63,6 @@ const CardPaymentModalComponent = ({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("🚀 ~ file: card-payment-modal.component..jsx:61 ~ data:", data);
|
|
||||||
//Initialize the intellipay window.
|
//Initialize the intellipay window.
|
||||||
const SetIntellipayCallbackFunctions = () => {
|
const SetIntellipayCallbackFunctions = () => {
|
||||||
console.log("*** Set IntelliPay callback functions.");
|
console.log("*** Set IntelliPay callback functions.");
|
||||||
@@ -74,16 +71,20 @@ const CardPaymentModalComponent = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
window.intellipay.runOnApproval(async function (response) {
|
window.intellipay.runOnApproval(async function (response) {
|
||||||
console.warn("*** Running On Approval Script ***");
|
//2024-04-25: Nothing is going to happen here anymore. We'll completely rely on the callback.
|
||||||
form.setFieldValue("paymentResponse", response);
|
//Add a slight delay to allow the refetch to properly get the data.
|
||||||
form.submit();
|
setTimeout(() => {
|
||||||
|
if (actions && actions.refetch && typeof actions.refetch === "function")
|
||||||
|
actions.refetch();
|
||||||
|
setLoading(false);
|
||||||
|
toggleModalVisible();
|
||||||
|
}, 750);
|
||||||
});
|
});
|
||||||
|
|
||||||
window.intellipay.runOnNonApproval(async function (response) {
|
window.intellipay.runOnNonApproval(async function (response) {
|
||||||
// Mutate unsuccessful payment
|
// Mutate unsuccessful payment
|
||||||
|
|
||||||
const { payments } = form.getFieldsValue();
|
const { payments } = form.getFieldsValue();
|
||||||
|
|
||||||
await insertPaymentResponse({
|
await insertPaymentResponse({
|
||||||
variables: {
|
variables: {
|
||||||
paymentResponse: payments.map((payment) => ({
|
paymentResponse: payments.map((payment) => ({
|
||||||
@@ -108,50 +109,9 @@ const CardPaymentModalComponent = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFinish = async (values) => {
|
|
||||||
try {
|
|
||||||
await insertPayment({
|
|
||||||
variables: {
|
|
||||||
paymentInput: values.payments.map((payment) => ({
|
|
||||||
amount: payment.amount,
|
|
||||||
transactionid: (values.paymentResponse.paymentid || "").toString(),
|
|
||||||
payer: t("payments.labels.customer"),
|
|
||||||
type: values.paymentResponse.cardbrand,
|
|
||||||
jobid: payment.jobid,
|
|
||||||
date: moment(Date.now()),
|
|
||||||
payment_responses: {
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
amount: payment.amount,
|
|
||||||
bodyshopid: bodyshop.id,
|
|
||||||
|
|
||||||
jobid: payment.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 () => {
|
const handleIntelliPayCharge = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
//Validate
|
//Validate
|
||||||
try {
|
try {
|
||||||
await form.validateFields();
|
await form.validateFields();
|
||||||
@@ -164,6 +124,7 @@ const CardPaymentModalComponent = ({
|
|||||||
const response = await axios.post("/intellipay/lightbox_credentials", {
|
const response = await axios.post("/intellipay/lightbox_credentials", {
|
||||||
bodyshop,
|
bodyshop,
|
||||||
refresh: !!window.intellipay,
|
refresh: !!window.intellipay,
|
||||||
|
paymentSplitMeta: form.getFieldsValue(),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (window.intellipay) {
|
if (window.intellipay) {
|
||||||
@@ -192,7 +153,6 @@ const CardPaymentModalComponent = ({
|
|||||||
<Card title="Card Payment">
|
<Card title="Card Payment">
|
||||||
<Spin spinning={loading}>
|
<Spin spinning={loading}>
|
||||||
<Form
|
<Form
|
||||||
onFinish={handleFinish}
|
|
||||||
form={form}
|
form={form}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
initialValues={{
|
initialValues={{
|
||||||
@@ -273,23 +233,14 @@ const CardPaymentModalComponent = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{() => {
|
{() => {
|
||||||
console.log("Updating the owner info section.");
|
|
||||||
//If all of the job ids have been fileld in, then query and update the IP field.
|
//If all of the job ids have been fileld in, then query and update the IP field.
|
||||||
const { payments } = form.getFieldsValue();
|
const { payments } = form.getFieldsValue();
|
||||||
if (
|
if (
|
||||||
payments?.length > 0 &&
|
payments?.length > 0 &&
|
||||||
payments?.filter((p) => p?.jobid).length === payments?.length
|
payments?.filter((p) => p?.jobid).length === payments?.length
|
||||||
) {
|
) {
|
||||||
console.log("**Calling refetch.");
|
|
||||||
refetch({ jobids: payments.map((p) => p.jobid) });
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Input
|
<Input
|
||||||
@@ -344,6 +295,13 @@ const CardPaymentModalComponent = ({
|
|||||||
value={totalAmountToCharge?.toFixed(2)}
|
value={totalAmountToCharge?.toFixed(2)}
|
||||||
hidden
|
hidden
|
||||||
/>
|
/>
|
||||||
|
<Input
|
||||||
|
className="ipayfield"
|
||||||
|
data-ipayname="comment"
|
||||||
|
//type="hidden"
|
||||||
|
value={btoa(JSON.stringify(payments))}
|
||||||
|
hidden
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
// data-ipayname="submit"
|
// data-ipayname="submit"
|
||||||
@@ -358,11 +316,6 @@ const CardPaymentModalComponent = ({
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
{/* Lightbox payment response when it is completed */}
|
|
||||||
<Form.Item name="paymentResponse" hidden>
|
|
||||||
<Input type="hidden" />
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
</Form>
|
||||||
</Spin>
|
</Spin>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -10,11 +10,15 @@ import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel
|
|||||||
import CourtesyCarReadiness from "../courtesy-car-readiness-select/courtesy-car-readiness-select.component";
|
import CourtesyCarReadiness from "../courtesy-car-readiness-select/courtesy-car-readiness-select.component";
|
||||||
import CourtesyCarStatus from "../courtesy-car-status-select/courtesy-car-status-select.component";
|
import CourtesyCarStatus from "../courtesy-car-status-select/courtesy-car-status-select.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";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
|
|
||||||
export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
export default function CourtesyCarCreateFormComponent({
|
||||||
|
form,
|
||||||
|
saveLoading,
|
||||||
|
newCC,
|
||||||
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
|
|
||||||
@@ -33,7 +37,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* <FormFieldsChanged form={form} /> */}
|
{newCC ? null : <FormFieldsChanged form={form} />}
|
||||||
<LayoutFormRow header={t("courtesycars.labels.vehicle")}>
|
<LayoutFormRow header={t("courtesycars.labels.vehicle")}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("courtesycars.fields.year")}
|
label={t("courtesycars.fields.year")}
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
|||||||
text: t("courtesycars.status.in"),
|
text: t("courtesycars.status.in"),
|
||||||
value: "courtesycars.status.in",
|
value: "courtesycars.status.in",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: t("courtesycars.status.inservice"),
|
||||||
|
value: "courtesycars.status.inservice",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: t("courtesycars.status.out"),
|
text: t("courtesycars.status.out"),
|
||||||
value: "courtesycars.status.out",
|
value: "courtesycars.status.out",
|
||||||
@@ -74,7 +78,7 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
|||||||
value: "courtesycars.status.leasereturn",
|
value: "courtesycars.status.leasereturn",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
onFilter: (value, record) => value.includes(record.status),
|
onFilter: (value, record) => record.status === value,
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
@@ -178,7 +182,7 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
|||||||
title: t("courtesycars.fields.fuel"),
|
title: t("courtesycars.fields.fuel"),
|
||||||
dataIndex: "fuel",
|
dataIndex: "fuel",
|
||||||
key: "fuel",
|
key: "fuel",
|
||||||
sorter: (a, b) => alphaSort(a.fuel, b.fuel),
|
sorter: (a, b) => a.fuel - b.fuel,
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "fuel" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "fuel" && state.sortedInfo.order,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
@@ -187,12 +191,14 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
|||||||
return t("courtesycars.labels.fuel.full");
|
return t("courtesycars.labels.fuel.full");
|
||||||
case 88:
|
case 88:
|
||||||
return t("courtesycars.labels.fuel.78");
|
return t("courtesycars.labels.fuel.78");
|
||||||
|
case 75:
|
||||||
|
return t("courtesycars.labels.fuel.34");
|
||||||
case 63:
|
case 63:
|
||||||
return t("courtesycars.labels.fuel.58");
|
return t("courtesycars.labels.fuel.58");
|
||||||
case 50:
|
case 50:
|
||||||
return t("courtesycars.labels.fuel.12");
|
return t("courtesycars.labels.fuel.12");
|
||||||
case 38:
|
case 38:
|
||||||
return t("courtesycars.labels.fuel.34");
|
return t("courtesycars.labels.fuel.38");
|
||||||
case 25:
|
case 25:
|
||||||
return t("courtesycars.labels.fuel.14");
|
return t("courtesycars.labels.fuel.14");
|
||||||
case 13:
|
case 13:
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
import {Card, Table, Tag} from "antd";
|
||||||
|
import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component";
|
||||||
|
import {useTranslation} from "react-i18next";
|
||||||
|
import React, {useEffect, useState} from "react";
|
||||||
|
import moment from "moment";
|
||||||
|
import DashboardRefreshRequired from "../refresh-required.component";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const fortyFiveDaysAgo = () => moment().subtract(45, 'days').toLocaleString();
|
||||||
|
|
||||||
|
export default function JobLifecycleDashboardComponent({data, bodyshop, ...cardProps}) {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [lifecycleData, setLifecycleData] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function getLifecycleData() {
|
||||||
|
if (data && data.job_lifecycle) {
|
||||||
|
setLoading(true);
|
||||||
|
const response = await axios.post("/job/lifecycle", {
|
||||||
|
jobids: data.job_lifecycle.map(x => x.id),
|
||||||
|
statuses: bodyshop.md_ro_statuses
|
||||||
|
});
|
||||||
|
setLifecycleData(response.data.durations);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getLifecycleData().catch(e => {
|
||||||
|
console.error(`Error in getLifecycleData: ${e}`);
|
||||||
|
})
|
||||||
|
}, [data, bodyshop]);
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: t('job_lifecycle.columns.status'),
|
||||||
|
dataIndex: 'status',
|
||||||
|
bgColor: 'red',
|
||||||
|
key: 'status',
|
||||||
|
render: (text, record) => {
|
||||||
|
return <Tag color={record.color}>{record.status}</Tag>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('job_lifecycle.columns.human_readable'),
|
||||||
|
dataIndex: 'humanReadable',
|
||||||
|
key: 'humanReadable',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('job_lifecycle.columns.status_count'),
|
||||||
|
key: 'statusCount',
|
||||||
|
render: (text, record) => {
|
||||||
|
return lifecycleData.statusCounts[record.status];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('job_lifecycle.columns.percentage'),
|
||||||
|
dataIndex: 'percentage',
|
||||||
|
key: 'percentage',
|
||||||
|
render: (text, record) => {
|
||||||
|
return record.percentage.toFixed(2) + '%';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!data) return null;
|
||||||
|
|
||||||
|
if (!data.job_lifecycle || !lifecycleData) return <DashboardRefreshRequired {...cardProps} />;
|
||||||
|
|
||||||
|
const extra = `${t('job_lifecycle.content.calculated_based_on')} ${lifecycleData.jobs} ${t('job_lifecycle.content.jobs_in_since')} ${fortyFiveDaysAgo()}`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card title={t("job_lifecycle.titles.dashboard")} {...cardProps}>
|
||||||
|
<LoadingSkeleton loading={loading}>
|
||||||
|
<div style={{overflow: 'scroll', height: "100%"}}>
|
||||||
|
<div id="bar-container" style={{
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
height: '100px',
|
||||||
|
textAlign: 'center',
|
||||||
|
borderRadius: '5px',
|
||||||
|
borderWidth: '5px',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderColor: '#f0f2f5',
|
||||||
|
margin: 0,
|
||||||
|
padding: 0
|
||||||
|
}}>
|
||||||
|
{lifecycleData.summations.map((key, index, array) => {
|
||||||
|
const isFirst = index === 0;
|
||||||
|
const isLast = index === array.length - 1;
|
||||||
|
return (
|
||||||
|
<div key={key.status} style={{
|
||||||
|
overflow: 'hidden',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
margin: 0,
|
||||||
|
padding: 0,
|
||||||
|
|
||||||
|
borderTop: '1px solid #f0f2f5',
|
||||||
|
borderBottom: '1px solid #f0f2f5',
|
||||||
|
borderLeft: isFirst ? '1px solid #f0f2f5' : undefined,
|
||||||
|
borderRight: isLast ? '1px solid #f0f2f5' : undefined,
|
||||||
|
|
||||||
|
backgroundColor: key.color,
|
||||||
|
width: `${key.percentage}%`
|
||||||
|
}}
|
||||||
|
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||||
|
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||||
|
>
|
||||||
|
|
||||||
|
{key.percentage > 15 ?
|
||||||
|
<>
|
||||||
|
<div>{key.roundedPercentage}</div>
|
||||||
|
<div style={{
|
||||||
|
backgroundColor: '#f0f2f5',
|
||||||
|
borderRadius: '5px',
|
||||||
|
paddingRight: '2px',
|
||||||
|
paddingLeft: '2px',
|
||||||
|
fontSize: '0.8rem',
|
||||||
|
}}>
|
||||||
|
{key.status}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<Card extra={extra} type='inner' title={t('job_lifecycle.content.legend_title')}
|
||||||
|
style={{marginTop: '10px'}}>
|
||||||
|
<div>
|
||||||
|
{lifecycleData.summations.map((key) => (
|
||||||
|
<Tag color={key.color} style={{width: '13vh', padding: '4px', margin: '4px'}}>
|
||||||
|
<div
|
||||||
|
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||||
|
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#f0f2f5',
|
||||||
|
color: '#000',
|
||||||
|
padding: '4px',
|
||||||
|
textAlign: 'center'
|
||||||
|
}}>
|
||||||
|
{key.status} [{lifecycleData.statusCounts[key.status]}] ({key.roundedPercentage})
|
||||||
|
</div>
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
<Card style={{marginTop: "5px"}} type='inner' title={t("job_lifecycle.titles.top_durations")}>
|
||||||
|
<Table size="small" pagination={false} columns={columns}
|
||||||
|
dataSource={lifecycleData.summations.sort((a, b) => b.value - a.value).slice(0, 3)}/>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</LoadingSkeleton>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const JobLifecycleDashboardGQL = `
|
||||||
|
job_lifecycle: jobs(where: {
|
||||||
|
actual_in: {
|
||||||
|
_gte: "${moment().subtract(45, 'days').toISOString()}"
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
id
|
||||||
|
actual_in
|
||||||
|
} `;
|
||||||
@@ -1,380 +1,391 @@
|
|||||||
import Icon, { SyncOutlined } from "@ant-design/icons";
|
import Icon, {SyncOutlined} from "@ant-design/icons";
|
||||||
import { gql, useMutation, useQuery } from "@apollo/client";
|
import {gql, useMutation, useQuery} from "@apollo/client";
|
||||||
import { Button, Dropdown, Menu, PageHeader, Space, notification } from "antd";
|
import {Button, Dropdown, Menu, notification, PageHeader, Space} from "antd";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import React, { useState } from "react";
|
import React, {useState} from "react";
|
||||||
import { Responsive, WidthProvider } from "react-grid-layout";
|
import {Responsive, WidthProvider} from "react-grid-layout";
|
||||||
import { useTranslation } from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import { MdClose } from "react-icons/md";
|
import {MdClose} from "react-icons/md";
|
||||||
import { connect } from "react-redux";
|
import {connect} from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import {createStructuredSelector} from "reselect";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import {logImEXEvent} from "../../firebase/firebase.utils";
|
||||||
import { UPDATE_DASHBOARD_LAYOUT } from "../../graphql/user.queries";
|
import {UPDATE_DASHBOARD_LAYOUT} from "../../graphql/user.queries";
|
||||||
import {
|
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
|
||||||
selectBodyshop,
|
|
||||||
selectCurrentUser,
|
|
||||||
} from "../../redux/user/user.selectors";
|
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import DashboardMonthlyEmployeeEfficiency, {
|
import DashboardMonthlyEmployeeEfficiency, {
|
||||||
DashboardMonthlyEmployeeEfficiencyGql,
|
DashboardMonthlyEmployeeEfficiencyGql,
|
||||||
} from "../dashboard-components/monthly-employee-efficiency/monthly-employee-efficiency.component";
|
} from "../dashboard-components/monthly-employee-efficiency/monthly-employee-efficiency.component";
|
||||||
import DashboardMonthlyJobCosting from "../dashboard-components/monthly-job-costing/monthly-job-costing.component";
|
import DashboardMonthlyJobCosting from "../dashboard-components/monthly-job-costing/monthly-job-costing.component";
|
||||||
import DashboardMonthlyLaborSales from "../dashboard-components/monthly-labor-sales/monthly-labor-sales.component";
|
import DashboardMonthlyLaborSales from "../dashboard-components/monthly-labor-sales/monthly-labor-sales.component";
|
||||||
import DashboardMonthlyPartsSales from "../dashboard-components/monthly-parts-sales/monthly-parts-sales.component";
|
import DashboardMonthlyPartsSales from "../dashboard-components/monthly-parts-sales/monthly-parts-sales.component";
|
||||||
import DashboardMonthlyRevenueGraph, {
|
import DashboardMonthlyRevenueGraph, {
|
||||||
DashboardMonthlyRevenueGraphGql,
|
DashboardMonthlyRevenueGraphGql,
|
||||||
} from "../dashboard-components/monthly-revenue-graph/monthly-revenue-graph.component";
|
} from "../dashboard-components/monthly-revenue-graph/monthly-revenue-graph.component";
|
||||||
import DashboardProjectedMonthlySales, {
|
import DashboardProjectedMonthlySales, {
|
||||||
DashboardProjectedMonthlySalesGql,
|
DashboardProjectedMonthlySalesGql,
|
||||||
} from "../dashboard-components/pojected-monthly-sales/projected-monthly-sales.component";
|
} from "../dashboard-components/pojected-monthly-sales/projected-monthly-sales.component";
|
||||||
import DashboardTotalProductionDollars from "../dashboard-components/total-production-dollars/total-production-dollars.component";
|
import DashboardTotalProductionDollars
|
||||||
|
from "../dashboard-components/total-production-dollars/total-production-dollars.component";
|
||||||
import DashboardTotalProductionHours, {
|
import DashboardTotalProductionHours, {
|
||||||
DashboardTotalProductionHoursGql,
|
DashboardTotalProductionHoursGql,
|
||||||
} from "../dashboard-components/total-production-hours/total-production-hours.component";
|
} from "../dashboard-components/total-production-hours/total-production-hours.component";
|
||||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||||
//Combination of the following:
|
//Combination of the following:
|
||||||
// /node_modules/react-grid-layout/css/styles.css
|
// /node_modules/react-grid-layout/css/styles.css
|
||||||
// /node_modules/react-resizable/css/styles.css
|
// /node_modules/react-resizable/css/styles.css
|
||||||
import DashboardScheduledInToday, {
|
import DashboardScheduledInToday, {
|
||||||
DashboardScheduledInTodayGql,
|
DashboardScheduledInTodayGql,
|
||||||
} from "../dashboard-components/scheduled-in-today/scheduled-in-today.component";
|
} from "../dashboard-components/scheduled-in-today/scheduled-in-today.component";
|
||||||
import DashboardScheduledOutToday, {
|
import DashboardScheduledOutToday, {
|
||||||
DashboardScheduledOutTodayGql,
|
DashboardScheduledOutTodayGql,
|
||||||
} from "../dashboard-components/scheduled-out-today/scheduled-out-today.component";
|
} from "../dashboard-components/scheduled-out-today/scheduled-out-today.component";
|
||||||
|
import JobLifecycleDashboardComponent, {
|
||||||
|
JobLifecycleDashboardGQL
|
||||||
|
} from "../dashboard-components/job-lifecycle/job-lifecycle-dashboard.component";
|
||||||
import "./dashboard-grid.styles.scss";
|
import "./dashboard-grid.styles.scss";
|
||||||
import { GenerateDashboardData } from "./dashboard-grid.utils";
|
import {GenerateDashboardData} from "./dashboard-grid.utils";
|
||||||
|
|
||||||
const ResponsiveReactGridLayout = WidthProvider(Responsive);
|
const ResponsiveReactGridLayout = WidthProvider(Responsive);
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
|
|
||||||
export function DashboardGridComponent({ currentUser, bodyshop }) {
|
export function DashboardGridComponent({currentUser, bodyshop}) {
|
||||||
const { t } = useTranslation();
|
const {t} = useTranslation();
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
...(bodyshop.associations[0].user.dashboardlayout
|
...(bodyshop.associations[0].user.dashboardlayout
|
||||||
? bodyshop.associations[0].user.dashboardlayout
|
? bodyshop.associations[0].user.dashboardlayout
|
||||||
: { items: [], layout: {}, layouts: [] }),
|
: {items: [], layout: {}, layouts: []}),
|
||||||
});
|
|
||||||
|
|
||||||
const { loading, error, data, refetch } = useQuery(
|
|
||||||
createDashboardQuery(state),
|
|
||||||
{ fetchPolicy: "network-only", nextFetchPolicy: "network-only" }
|
|
||||||
);
|
|
||||||
|
|
||||||
const [updateLayout] = useMutation(UPDATE_DASHBOARD_LAYOUT);
|
|
||||||
|
|
||||||
const handleLayoutChange = async (layout, layouts) => {
|
|
||||||
logImEXEvent("dashboard_change_layout");
|
|
||||||
|
|
||||||
setState({ ...state, layout, layouts });
|
|
||||||
|
|
||||||
const result = await updateLayout({
|
|
||||||
variables: {
|
|
||||||
email: currentUser.email,
|
|
||||||
layout: { ...state, layout, layouts },
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
if (!!result.errors) {
|
|
||||||
notification["error"]({
|
|
||||||
message: t("dashboard.errors.updatinglayout", {
|
|
||||||
message: JSON.stringify(result.errors),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handleRemoveComponent = (key) => {
|
|
||||||
logImEXEvent("dashboard_remove_component", { name: key });
|
|
||||||
const idxToRemove = state.items.findIndex((i) => i.i === key);
|
|
||||||
|
|
||||||
const items = _.cloneDeep(state.items);
|
const {loading, error, data, refetch} = useQuery(
|
||||||
|
createDashboardQuery(state),
|
||||||
|
{fetchPolicy: "network-only", nextFetchPolicy: "network-only"}
|
||||||
|
);
|
||||||
|
|
||||||
items.splice(idxToRemove, 1);
|
const [updateLayout] = useMutation(UPDATE_DASHBOARD_LAYOUT);
|
||||||
setState({ ...state, items });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddComponent = (e) => {
|
const handleLayoutChange = async (layout, layouts) => {
|
||||||
logImEXEvent("dashboard_add_component", { name: e });
|
logImEXEvent("dashboard_change_layout");
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
items: [
|
|
||||||
...state.items,
|
|
||||||
{
|
|
||||||
i: e.key,
|
|
||||||
x: (state.items.length * 2) % (state.cols || 12),
|
|
||||||
y: 99, // puts it at the bottom
|
|
||||||
w: componentList[e.key].w || 2,
|
|
||||||
h: componentList[e.key].h || 2,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const dashboarddata = React.useMemo(
|
setState({...state, layout, layouts});
|
||||||
() => GenerateDashboardData(data),
|
|
||||||
[data]
|
|
||||||
);
|
|
||||||
const existingLayoutKeys = state.items.map((i) => i.i);
|
|
||||||
const addComponentOverlay = (
|
|
||||||
<Menu onClick={handleAddComponent}>
|
|
||||||
{Object.keys(componentList).map((key) => (
|
|
||||||
<Menu.Item
|
|
||||||
key={key}
|
|
||||||
value={key}
|
|
||||||
disabled={existingLayoutKeys.includes(key)}
|
|
||||||
>
|
|
||||||
{componentList[key].label}
|
|
||||||
</Menu.Item>
|
|
||||||
))}
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
const result = await updateLayout({
|
||||||
|
variables: {
|
||||||
return (
|
email: currentUser.email,
|
||||||
<div>
|
layout: {...state, layout, layouts},
|
||||||
<PageHeader
|
},
|
||||||
extra={
|
});
|
||||||
<Space>
|
if (!!result.errors) {
|
||||||
<Button onClick={() => refetch()}>
|
notification["error"]({
|
||||||
<SyncOutlined />
|
message: t("dashboard.errors.updatinglayout", {
|
||||||
</Button>
|
message: JSON.stringify(result.errors),
|
||||||
<Dropdown overlay={addComponentOverlay} trigger={["click"]}>
|
}),
|
||||||
<Button>{t("dashboard.actions.addcomponent")}</Button>
|
});
|
||||||
</Dropdown>
|
|
||||||
</Space>
|
|
||||||
}
|
}
|
||||||
/>
|
};
|
||||||
|
const handleRemoveComponent = (key) => {
|
||||||
|
logImEXEvent("dashboard_remove_component", {name: key});
|
||||||
|
const idxToRemove = state.items.findIndex((i) => i.i === key);
|
||||||
|
|
||||||
<ResponsiveReactGridLayout
|
const items = _.cloneDeep(state.items);
|
||||||
className="layout"
|
|
||||||
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
|
items.splice(idxToRemove, 1);
|
||||||
cols={{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }}
|
setState({...state, items});
|
||||||
width="100%"
|
};
|
||||||
layouts={state.layouts}
|
|
||||||
onLayoutChange={handleLayoutChange}
|
const handleAddComponent = (e) => {
|
||||||
// onBreakpointChange={onBreakpointChange}
|
logImEXEvent("dashboard_add_component", {name: e});
|
||||||
>
|
setState({
|
||||||
{state.items.map((item, index) => {
|
...state,
|
||||||
const TheComponent = componentList[item.i].component;
|
items: [
|
||||||
return (
|
...state.items,
|
||||||
<div
|
{
|
||||||
key={item.i}
|
i: e.key,
|
||||||
data-grid={{
|
x: (state.items.length * 2) % (state.cols || 12),
|
||||||
...item,
|
y: 99, // puts it at the bottom
|
||||||
minH: componentList[item.i].minH || 1,
|
w: componentList[e.key].w || 2,
|
||||||
minW: componentList[item.i].minW || 1,
|
h: componentList[e.key].h || 2,
|
||||||
}}
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const dashboarddata = React.useMemo(
|
||||||
|
() => GenerateDashboardData(data),
|
||||||
|
[data]
|
||||||
|
);
|
||||||
|
const existingLayoutKeys = state.items.map((i) => i.i);
|
||||||
|
const addComponentOverlay = (
|
||||||
|
<Menu onClick={handleAddComponent}>
|
||||||
|
{Object.keys(componentList).map((key) => (
|
||||||
|
<Menu.Item
|
||||||
|
key={key}
|
||||||
|
value={key}
|
||||||
|
disabled={existingLayoutKeys.includes(key)}
|
||||||
|
>
|
||||||
|
{componentList[key].label}
|
||||||
|
</Menu.Item>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) return <AlertComponent message={error.message} type="error"/>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PageHeader
|
||||||
|
extra={
|
||||||
|
<Space>
|
||||||
|
<Button onClick={() => refetch()}>
|
||||||
|
<SyncOutlined/>
|
||||||
|
</Button>
|
||||||
|
<Dropdown overlay={addComponentOverlay} trigger={["click"]}>
|
||||||
|
<Button>{t("dashboard.actions.addcomponent")}</Button>
|
||||||
|
</Dropdown>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ResponsiveReactGridLayout
|
||||||
|
className="layout"
|
||||||
|
breakpoints={{lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0}}
|
||||||
|
cols={{lg: 12, md: 10, sm: 6, xs: 4, xxs: 2}}
|
||||||
|
width="100%"
|
||||||
|
layouts={state.layouts}
|
||||||
|
onLayoutChange={handleLayoutChange}
|
||||||
|
// onBreakpointChange={onBreakpointChange}
|
||||||
>
|
>
|
||||||
<LoadingSkeleton loading={loading}>
|
{state.items.map((item, index) => {
|
||||||
<Icon
|
const TheComponent = componentList[item.i].component;
|
||||||
component={MdClose}
|
return (
|
||||||
key={item.i}
|
<div
|
||||||
style={{
|
key={item.i}
|
||||||
position: "absolute",
|
data-grid={{
|
||||||
zIndex: "2",
|
...item,
|
||||||
right: ".25rem",
|
minH: componentList[item.i].minH || 1,
|
||||||
top: ".25rem",
|
minW: componentList[item.i].minW || 1,
|
||||||
cursor: "pointer",
|
}}
|
||||||
}}
|
>
|
||||||
onClick={() => handleRemoveComponent(item.i)}
|
<LoadingSkeleton loading={loading}>
|
||||||
/>
|
<Icon
|
||||||
<TheComponent className="dashboard-card" data={dashboarddata} />
|
component={MdClose}
|
||||||
</LoadingSkeleton>
|
key={item.i}
|
||||||
</div>
|
style={{
|
||||||
);
|
position: "absolute",
|
||||||
})}
|
zIndex: "2",
|
||||||
</ResponsiveReactGridLayout>
|
right: ".25rem",
|
||||||
</div>
|
top: ".25rem",
|
||||||
);
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onClick={() => handleRemoveComponent(item.i)}
|
||||||
|
/>
|
||||||
|
<TheComponent className="dashboard-card" bodyshop={bodyshop} data={dashboarddata}/>
|
||||||
|
</LoadingSkeleton>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ResponsiveReactGridLayout>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(DashboardGridComponent);
|
)(DashboardGridComponent);
|
||||||
|
|
||||||
const componentList = {
|
const componentList = {
|
||||||
ProductionDollars: {
|
ProductionDollars: {
|
||||||
label: i18next.t("dashboard.titles.productiondollars"),
|
label: i18next.t("dashboard.titles.productiondollars"),
|
||||||
component: DashboardTotalProductionDollars,
|
component: DashboardTotalProductionDollars,
|
||||||
gqlFragment: null,
|
gqlFragment: null,
|
||||||
w: 1,
|
w: 1,
|
||||||
h: 1,
|
h: 1,
|
||||||
minW: 2,
|
minW: 2,
|
||||||
minH: 1,
|
minH: 1,
|
||||||
},
|
},
|
||||||
ProductionHours: {
|
ProductionHours: {
|
||||||
label: i18next.t("dashboard.titles.productionhours"),
|
label: i18next.t("dashboard.titles.productionhours"),
|
||||||
component: DashboardTotalProductionHours,
|
component: DashboardTotalProductionHours,
|
||||||
gqlFragment: DashboardTotalProductionHoursGql,
|
gqlFragment: DashboardTotalProductionHoursGql,
|
||||||
w: 3,
|
w: 3,
|
||||||
h: 1,
|
h: 1,
|
||||||
minW: 3,
|
minW: 3,
|
||||||
minH: 1,
|
minH: 1,
|
||||||
},
|
},
|
||||||
ProjectedMonthlySales: {
|
ProjectedMonthlySales: {
|
||||||
label: i18next.t("dashboard.titles.projectedmonthlysales"),
|
label: i18next.t("dashboard.titles.projectedmonthlysales"),
|
||||||
component: DashboardProjectedMonthlySales,
|
component: DashboardProjectedMonthlySales,
|
||||||
gqlFragment: DashboardProjectedMonthlySalesGql,
|
gqlFragment: DashboardProjectedMonthlySalesGql,
|
||||||
w: 2,
|
w: 2,
|
||||||
h: 1,
|
h: 1,
|
||||||
minW: 2,
|
minW: 2,
|
||||||
minH: 1,
|
minH: 1,
|
||||||
},
|
},
|
||||||
MonthlyRevenueGraph: {
|
MonthlyRevenueGraph: {
|
||||||
label: i18next.t("dashboard.titles.monthlyrevenuegraph"),
|
label: i18next.t("dashboard.titles.monthlyrevenuegraph"),
|
||||||
component: DashboardMonthlyRevenueGraph,
|
component: DashboardMonthlyRevenueGraph,
|
||||||
gqlFragment: DashboardMonthlyRevenueGraphGql,
|
gqlFragment: DashboardMonthlyRevenueGraphGql,
|
||||||
w: 4,
|
w: 4,
|
||||||
h: 2,
|
h: 2,
|
||||||
minW: 4,
|
minW: 4,
|
||||||
minH: 2,
|
minH: 2,
|
||||||
},
|
},
|
||||||
MonthlyJobCosting: {
|
MonthlyJobCosting: {
|
||||||
label: i18next.t("dashboard.titles.monthlyjobcosting"),
|
label: i18next.t("dashboard.titles.monthlyjobcosting"),
|
||||||
component: DashboardMonthlyJobCosting,
|
component: DashboardMonthlyJobCosting,
|
||||||
gqlFragment: null,
|
gqlFragment: null,
|
||||||
minW: 6,
|
minW: 6,
|
||||||
minH: 3,
|
minH: 3,
|
||||||
w: 6,
|
w: 6,
|
||||||
h: 3,
|
h: 3,
|
||||||
},
|
},
|
||||||
MonthlyPartsSales: {
|
MonthlyPartsSales: {
|
||||||
label: i18next.t("dashboard.titles.monthlypartssales"),
|
label: i18next.t("dashboard.titles.monthlypartssales"),
|
||||||
component: DashboardMonthlyPartsSales,
|
component: DashboardMonthlyPartsSales,
|
||||||
gqlFragment: null,
|
gqlFragment: null,
|
||||||
minW: 2,
|
minW: 2,
|
||||||
minH: 2,
|
minH: 2,
|
||||||
w: 2,
|
w: 2,
|
||||||
h: 2,
|
h: 2,
|
||||||
},
|
},
|
||||||
MonthlyLaborSales: {
|
MonthlyLaborSales: {
|
||||||
label: i18next.t("dashboard.titles.monthlylaborsales"),
|
label: i18next.t("dashboard.titles.monthlylaborsales"),
|
||||||
component: DashboardMonthlyLaborSales,
|
component: DashboardMonthlyLaborSales,
|
||||||
gqlFragment: null,
|
gqlFragment: null,
|
||||||
minW: 2,
|
minW: 2,
|
||||||
minH: 2,
|
minH: 2,
|
||||||
w: 2,
|
w: 2,
|
||||||
h: 2,
|
h: 2,
|
||||||
},
|
},
|
||||||
MonthlyEmployeeEfficency: {
|
// Typo in Efficency should be Efficiency, but changing it would reset users dashboard settings
|
||||||
label: i18next.t("dashboard.titles.monthlyemployeeefficiency"),
|
MonthlyEmployeeEfficency: {
|
||||||
component: DashboardMonthlyEmployeeEfficiency,
|
label: i18next.t("dashboard.titles.monthlyemployeeefficiency"),
|
||||||
gqlFragment: DashboardMonthlyEmployeeEfficiencyGql,
|
component: DashboardMonthlyEmployeeEfficiency,
|
||||||
minW: 2,
|
gqlFragment: DashboardMonthlyEmployeeEfficiencyGql,
|
||||||
minH: 2,
|
minW: 2,
|
||||||
w: 2,
|
minH: 2,
|
||||||
h: 2,
|
w: 2,
|
||||||
},
|
h: 2,
|
||||||
ScheduleInToday: {
|
},
|
||||||
label: i18next.t("dashboard.titles.scheduledintoday"),
|
ScheduleInToday: {
|
||||||
component: DashboardScheduledInToday,
|
label: i18next.t("dashboard.titles.scheduledintoday"),
|
||||||
gqlFragment: DashboardScheduledInTodayGql,
|
component: DashboardScheduledInToday,
|
||||||
minW: 6,
|
gqlFragment: DashboardScheduledInTodayGql,
|
||||||
minH: 2,
|
minW: 6,
|
||||||
w: 10,
|
minH: 2,
|
||||||
h: 3,
|
w: 10,
|
||||||
},
|
h: 3,
|
||||||
ScheduleOutToday: {
|
},
|
||||||
label: i18next.t("dashboard.titles.scheduledouttoday"),
|
ScheduleOutToday: {
|
||||||
component: DashboardScheduledOutToday,
|
label: i18next.t("dashboard.titles.scheduledouttoday"),
|
||||||
gqlFragment: DashboardScheduledOutTodayGql,
|
component: DashboardScheduledOutToday,
|
||||||
minW: 6,
|
gqlFragment: DashboardScheduledOutTodayGql,
|
||||||
minH: 2,
|
minW: 6,
|
||||||
w: 10,
|
minH: 2,
|
||||||
h: 3,
|
w: 10,
|
||||||
},
|
h: 3,
|
||||||
|
},
|
||||||
|
JobLifecycle: {
|
||||||
|
label: i18next.t("dashboard.titles.joblifecycle"),
|
||||||
|
component: JobLifecycleDashboardComponent,
|
||||||
|
gqlFragment: JobLifecycleDashboardGQL,
|
||||||
|
minW: 6,
|
||||||
|
minH: 3,
|
||||||
|
w: 6,
|
||||||
|
h: 3,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const createDashboardQuery = (state) => {
|
const createDashboardQuery = (state) => {
|
||||||
const componentBasedAdditions =
|
const componentBasedAdditions =
|
||||||
state &&
|
state &&
|
||||||
Array.isArray(state.layout) &&
|
Array.isArray(state.layout) &&
|
||||||
state.layout
|
state.layout
|
||||||
.map((item, index) => componentList[item.i].gqlFragment || "")
|
.map((item, index) => componentList[item.i].gqlFragment || "")
|
||||||
.join("");
|
.join("");
|
||||||
return gql`
|
return gql`
|
||||||
query QUERY_DASHBOARD_DETAILS { ${componentBasedAdditions || ""}
|
query QUERY_DASHBOARD_DETAILS { ${componentBasedAdditions || ""}
|
||||||
monthly_sales: jobs(where: {_and: [
|
monthly_sales: jobs(where: {_and: [
|
||||||
{ voided: {_eq: false}},
|
{ voided: {_eq: false}},
|
||||||
{date_invoiced: {_gte: "${moment()
|
{date_invoiced: {_gte: "${moment()
|
||||||
.startOf("month")
|
.startOf("month")
|
||||||
.startOf("day")
|
.startOf("day")
|
||||||
.toISOString()}"}}, {date_invoiced: {_lte: "${moment()
|
.toISOString()}"}}, {date_invoiced: {_lte: "${moment()
|
||||||
.endOf("month")
|
.endOf("month")
|
||||||
.endOf("day")
|
.endOf("day")
|
||||||
.toISOString()}"}}]}) {
|
.toISOString()}"}}]}) {
|
||||||
id
|
id
|
||||||
ro_number
|
ro_number
|
||||||
date_invoiced
|
date_invoiced
|
||||||
job_totals
|
job_totals
|
||||||
rate_la1
|
rate_la1
|
||||||
rate_la2
|
rate_la2
|
||||||
rate_la3
|
rate_la3
|
||||||
rate_la4
|
rate_la4
|
||||||
rate_laa
|
rate_laa
|
||||||
rate_lab
|
rate_lab
|
||||||
rate_lad
|
rate_lad
|
||||||
rate_lae
|
rate_lae
|
||||||
rate_laf
|
rate_laf
|
||||||
rate_lag
|
rate_lag
|
||||||
rate_lam
|
rate_lam
|
||||||
rate_lar
|
rate_lar
|
||||||
rate_las
|
rate_las
|
||||||
rate_lau
|
rate_lau
|
||||||
rate_ma2s
|
rate_ma2s
|
||||||
rate_ma2t
|
rate_ma2t
|
||||||
rate_ma3s
|
rate_ma3s
|
||||||
rate_mabl
|
rate_mabl
|
||||||
rate_macs
|
rate_macs
|
||||||
rate_mahw
|
rate_mahw
|
||||||
rate_mapa
|
rate_mapa
|
||||||
rate_mash
|
rate_mash
|
||||||
rate_matd
|
rate_matd
|
||||||
joblines(where: { removed: { _eq: false } }) {
|
joblines(where: { removed: { _eq: false } }) {
|
||||||
id
|
id
|
||||||
mod_lbr_ty
|
mod_lbr_ty
|
||||||
mod_lb_hrs
|
mod_lb_hrs
|
||||||
act_price
|
act_price
|
||||||
part_qty
|
part_qty
|
||||||
part_type
|
part_type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
production_jobs: jobs(where: { inproduction: { _eq: true } }) {
|
production_jobs: jobs(where: { inproduction: { _eq: true } }) {
|
||||||
id
|
id
|
||||||
ro_number
|
ro_number
|
||||||
ins_co_nm
|
ins_co_nm
|
||||||
job_totals
|
job_totals
|
||||||
joblines(where: { removed: { _eq: false } }) {
|
joblines(where: { removed: { _eq: false } }) {
|
||||||
id
|
id
|
||||||
mod_lbr_ty
|
mod_lbr_ty
|
||||||
mod_lb_hrs
|
mod_lb_hrs
|
||||||
act_price
|
act_price
|
||||||
part_qty
|
part_qty
|
||||||
part_type
|
part_type
|
||||||
}
|
}
|
||||||
labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) {
|
labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) {
|
||||||
aggregate {
|
aggregate {
|
||||||
sum {
|
sum {
|
||||||
mod_lb_hrs
|
mod_lb_hrs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) {
|
larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) {
|
||||||
aggregate {
|
aggregate {
|
||||||
sum {
|
sum {
|
||||||
mod_lb_hrs
|
mod_lb_hrs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,10 +18,8 @@ export default function JobDetailCardsTotalsComponent({ loading, data }) {
|
|||||||
/>
|
/>
|
||||||
<Statistic
|
<Statistic
|
||||||
className="imex-flex-row__margin-large"
|
className="imex-flex-row__margin-large"
|
||||||
title={t("jobs.fields.ded_amt")}
|
title={t("jobs.fields.customerowing")}
|
||||||
value={Dinero({
|
value={Dinero(data.job_totals.totals.custPayable.total).toFormat()}
|
||||||
amount: Math.round((data.ded_amt || 0) * 100),
|
|
||||||
}).toFormat()}
|
|
||||||
/>
|
/>
|
||||||
<Statistic
|
<Statistic
|
||||||
className="imex-flex-row__margin-large"
|
className="imex-flex-row__margin-large"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
import { Col, Divider, Row, Skeleton, Space, Timeline, Typography } from "antd";
|
import { Col, Row, Skeleton, Timeline, Typography } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
@@ -31,15 +31,27 @@ export default function JobLinesExpander({ jobline, jobid }) {
|
|||||||
{data.parts_order_lines.length > 0 ? (
|
{data.parts_order_lines.length > 0 ? (
|
||||||
data.parts_order_lines.map((line) => (
|
data.parts_order_lines.map((line) => (
|
||||||
<Timeline.Item key={line.id}>
|
<Timeline.Item key={line.id}>
|
||||||
<Space split={<Divider type="vertical" />} wrap>
|
<Row wrap>
|
||||||
<Link
|
<Col span={4}>
|
||||||
to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`}
|
<Link
|
||||||
>
|
to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`}
|
||||||
{line.parts_order.order_number}
|
>
|
||||||
</Link>
|
{line.parts_order.order_number}
|
||||||
<DateFormatter>{line.parts_order.order_date}</DateFormatter>
|
</Link>
|
||||||
{line.parts_order.vendor.name}
|
</Col>
|
||||||
</Space>
|
<Col span={4}>
|
||||||
|
<DateFormatter>{line.parts_order.order_date}</DateFormatter>
|
||||||
|
</Col>
|
||||||
|
<Col span={4}>{line.parts_order.vendor.name}</Col>
|
||||||
|
{line.backordered_eta ? (
|
||||||
|
<Col span={4}>
|
||||||
|
<span>
|
||||||
|
{`${t("parts_orders.fields.backordered_eta")}: `}
|
||||||
|
<DateFormatter>{line.backordered_eta}</DateFormatter>
|
||||||
|
</span>
|
||||||
|
</Col>
|
||||||
|
) : null}
|
||||||
|
</Row>
|
||||||
</Timeline.Item>
|
</Timeline.Item>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
@@ -71,7 +83,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>
|
||||||
@@ -84,7 +96,7 @@ export default function JobLinesExpander({ jobline, jobid }) {
|
|||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<Timeline.Item>
|
<Timeline.Item>
|
||||||
{t("parts_orders.labels.notyetordered")}
|
{t("bills.labels.nobilllines")}
|
||||||
</Timeline.Item>
|
</Timeline.Item>
|
||||||
)}
|
)}
|
||||||
</Timeline>
|
</Timeline>
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ export function JobEmployeeAssignments({
|
|||||||
jobRO,
|
jobRO,
|
||||||
body,
|
body,
|
||||||
refinish,
|
refinish,
|
||||||
|
|
||||||
prep,
|
prep,
|
||||||
csr,
|
csr,
|
||||||
handleAdd,
|
handleAdd,
|
||||||
@@ -78,7 +77,7 @@ export function JobEmployeeAssignments({
|
|||||||
setVisibility(false);
|
setVisibility(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Assign
|
{t("allocations.actions.assign")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => setVisibility(false)}>Close</Button>
|
<Button onClick={() => setVisibility(false)}>Close</Button>
|
||||||
</Space>
|
</Space>
|
||||||
|
|||||||
@@ -43,13 +43,13 @@ export function JobEmployeeAssignmentsContainer({
|
|||||||
});
|
});
|
||||||
if (refetch) refetch();
|
if (refetch) refetch();
|
||||||
|
|
||||||
insertAuditTrail({
|
if (!!!result.errors) {
|
||||||
jobid: job.id,
|
insertAuditTrail({
|
||||||
operation: AuditTrailMapping.jobassignmentchange(operation, name),
|
jobid: job.id,
|
||||||
type: "jobassignmentchange",
|
operation: AuditTrailMapping.jobassignmentchange(operation, name),
|
||||||
});
|
type: "jobassignmentchange",
|
||||||
|
});
|
||||||
if (!!result.errors) {
|
} else {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
message: t("jobs.errors.assigning", {
|
message: t("jobs.errors.assigning", {
|
||||||
message: JSON.stringify(result.errors),
|
message: JSON.stringify(result.errors),
|
||||||
@@ -67,18 +67,19 @@ export function JobEmployeeAssignmentsContainer({
|
|||||||
variables: { jobId: job.id, job: { [empAssignment]: null } },
|
variables: { jobId: job.id, job: { [empAssignment]: null } },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!!result.errors) {
|
if (!!!result.errors) {
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid: job.id,
|
||||||
|
operation: AuditTrailMapping.jobassignmentremoved(operation),
|
||||||
|
type: "jobassignmentremoved",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
message: t("jobs.errors.assigning", {
|
message: t("jobs.errors.assigning", {
|
||||||
message: JSON.stringify(result.errors),
|
message: JSON.stringify(result.errors),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
insertAuditTrail({
|
|
||||||
jobid: job.id,
|
|
||||||
operation: AuditTrailMapping.jobassignmentremoved(operation),
|
|
||||||
type: "jobassignmentremoved",
|
|
||||||
});
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export default function JobReconciliationBillsTable({
|
|||||||
state.sortedInfo.order,
|
state.sortedInfo.order,
|
||||||
|
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Checkbox disabled checked={record.bill.is_credit_memo} />
|
<Checkbox checked={record.bill.is_credit_memo} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
import { Collapse, Form, Input, InputNumber, Select, Switch } from "antd";
|
import {
|
||||||
|
Collapse,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
Select,
|
||||||
|
Space,
|
||||||
|
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";
|
||||||
@@ -10,6 +18,8 @@ import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
|||||||
import FormItemPhone, {
|
import FormItemPhone, {
|
||||||
PhoneItemFormatterValidation,
|
PhoneItemFormatterValidation,
|
||||||
} from "../form-items-formatted/phone-form-item.component";
|
} from "../form-items-formatted/phone-form-item.component";
|
||||||
|
import JobsDetailChangeEstimator from "../jobs-detail-change-estimator/jobs-detail-change-estimator.component";
|
||||||
|
import JobsDetailChangeFilehandler from "../jobs-detail-change-filehandler/jobs-detail-change-filehandler.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 JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
|
import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
|
||||||
@@ -25,6 +35,15 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { getFieldValue } = form;
|
const { getFieldValue } = form;
|
||||||
|
|
||||||
|
const handleInsCoChange = (value) => {
|
||||||
|
const selectedCompany = bodyshop.md_ins_cos.find((s) => s.name === value);
|
||||||
|
if (selectedCompany) {
|
||||||
|
form.setFieldValue("ins_addr1", selectedCompany.street1);
|
||||||
|
form.setFieldValue("ins_city", selectedCompany.city);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Collapse defaultActiveKey="insurance">
|
<Collapse defaultActiveKey="insurance">
|
||||||
@@ -34,26 +53,20 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
|||||||
forceRender
|
forceRender
|
||||||
>
|
>
|
||||||
<LayoutFormRow>
|
<LayoutFormRow>
|
||||||
<Form.Item label={t("jobs.fields.ins_co_id")} name="ins_co_id">
|
<Form.Item label={t("jobs.fields.clm_no")} name="clm_no">
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.policy_no")} name="policy_no">
|
<Form.Item label={t("jobs.fields.policy_no")} name="policy_no">
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.clm_no")} name="clm_no">
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("jobs.fields.regie_number")}
|
label={t("jobs.fields.regie_number")}
|
||||||
name="regie_number"
|
name="regie_number"
|
||||||
>
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
|
|
||||||
<FormDatePicker />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm">
|
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm">
|
||||||
<Select>
|
<Select onChange={handleInsCoChange}>
|
||||||
{bodyshop.md_ins_cos.map((s) => (
|
{bodyshop.md_ins_cos.map((s) => (
|
||||||
<Select.Option key={s.name} value={s.name}>
|
<Select.Option key={s.name} value={s.name}>
|
||||||
{s.name}
|
{s.name}
|
||||||
@@ -67,7 +80,15 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
|||||||
<Form.Item label={t("jobs.fields.ins_city")} name="ins_city">
|
<Form.Item label={t("jobs.fields.ins_city")} name="ins_city">
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.ins_ct_ln")} name="ins_ct_ln">
|
<Form.Item
|
||||||
|
label={
|
||||||
|
<Space>
|
||||||
|
{t("jobs.fields.ins_ct_ln")}
|
||||||
|
<JobsDetailChangeFilehandler form={form} />
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
name="ins_ct_ln"
|
||||||
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.ins_ct_fn")} name="ins_ct_fn">
|
<Form.Item label={t("jobs.fields.ins_ct_fn")} name="ins_ct_fn">
|
||||||
@@ -95,11 +116,24 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
|||||||
>
|
>
|
||||||
<FormItemEmail email={getFieldValue("ins_ea")} />
|
<FormItemEmail email={getFieldValue("ins_ea")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
|
||||||
|
<FormDatePicker />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.kmin")} name="kmin">
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.est_co_nm")} name="est_co_nm">
|
<Form.Item label={t("jobs.fields.est_co_nm")} name="est_co_nm">
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.est_ct_fn")} name="est_ct_fn">
|
<Form.Item
|
||||||
|
label={
|
||||||
|
<Space>
|
||||||
|
{t("jobs.fields.est_ct_fn")}
|
||||||
|
<JobsDetailChangeEstimator form={form} />
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
name="est_ct_fn"
|
||||||
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.est_ct_ln")} name="est_ct_ln">
|
<Form.Item label={t("jobs.fields.est_ct_ln")} name="est_ct_ln">
|
||||||
|
|||||||
@@ -37,6 +37,15 @@ const lossColDamage = { sm: { span: 24 }, md: { span: 6 }, lg: { span: 4 } };
|
|||||||
export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
||||||
const { getFieldValue } = form;
|
const { getFieldValue } = form;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const handleInsCoChange = (value) => {
|
||||||
|
const selectedCompany = bodyshop.md_ins_cos.find((s) => s.name === value);
|
||||||
|
if (selectedCompany) {
|
||||||
|
form.setFieldValue("ins_addr1", selectedCompany.street1);
|
||||||
|
form.setFieldValue("ins_city", selectedCompany.city);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<FormRow header={t("jobs.forms.claiminfo")}>
|
<FormRow header={t("jobs.forms.claiminfo")}>
|
||||||
@@ -71,7 +80,7 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm">
|
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm">
|
||||||
<Select disabled={jobRO}>
|
<Select disabled={jobRO} onChange={handleInsCoChange}>
|
||||||
{bodyshop.md_ins_cos.map((s) => (
|
{bodyshop.md_ins_cos.map((s) => (
|
||||||
<Select.Option key={s.name} value={s.name}>
|
<Select.Option key={s.name} value={s.name}>
|
||||||
{s.name}
|
{s.name}
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ export function JobsDetailHeaderActions({
|
|||||||
disabled={!job.converted}
|
disabled={!job.converted}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCardPaymentContext({
|
setCardPaymentContext({
|
||||||
actions: {},
|
actions: { refetch },
|
||||||
context: { jobid: job.id },
|
context: { jobid: job.id },
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import {
|
|||||||
PauseCircleOutlined,
|
PauseCircleOutlined,
|
||||||
WarningFilled,
|
WarningFilled,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { Card, Col, Row, Space, Tag, Tooltip } from "antd";
|
import { Card, Col, Divider, Row, Space, Tag, Tooltip } from "antd";
|
||||||
|
import moment from "moment";
|
||||||
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";
|
||||||
@@ -62,6 +63,13 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
|||||||
${job.v_make_desc || ""}
|
${job.v_make_desc || ""}
|
||||||
${job.v_model_desc || ""}`.trim();
|
${job.v_model_desc || ""}`.trim();
|
||||||
|
|
||||||
|
const bodyHrs = job.joblines
|
||||||
|
.filter((j) => j.mod_lbr_ty !== "LAR")
|
||||||
|
.reduce((acc, val) => acc + val.mod_lb_hrs, 0);
|
||||||
|
const refinishHrs = job.joblines
|
||||||
|
.filter((line) => line.mod_lbr_ty === "LAR")
|
||||||
|
.reduce((acc, val) => acc + val.mod_lb_hrs, 0);
|
||||||
|
|
||||||
const ownerTitle = OwnerNameDisplayFunction(job).trim();
|
const ownerTitle = OwnerNameDisplayFunction(job).trim();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -93,7 +101,13 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
|||||||
{job.status === bodyshop.md_ro_statuses.default_scheduled &&
|
{job.status === bodyshop.md_ro_statuses.default_scheduled &&
|
||||||
job.scheduled_in ? (
|
job.scheduled_in ? (
|
||||||
<Tag>
|
<Tag>
|
||||||
<DateTimeFormatter>{job.scheduled_in}</DateTimeFormatter>
|
<Link
|
||||||
|
to={`/manage/schedule?date=${moment(
|
||||||
|
job.scheduled_in
|
||||||
|
).format("YYYY-MM-DD")}`}
|
||||||
|
>
|
||||||
|
<DateTimeFormatter>{job.scheduled_in}</DateTimeFormatter>
|
||||||
|
</Link>
|
||||||
</Tag>
|
</Tag>
|
||||||
) : null}
|
) : null}
|
||||||
</Space>
|
</Space>
|
||||||
@@ -211,6 +225,12 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
|||||||
{job.owner?.tax_number || ""}
|
{job.owner?.tax_number || ""}
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
)}
|
)}
|
||||||
|
<DataLabel
|
||||||
|
label={t("owners.fields.note")}
|
||||||
|
valueStyle={{ overflow: "hidden", textOverflow: "ellipsis" }}
|
||||||
|
>
|
||||||
|
{job.owner?.note || ""}
|
||||||
|
</DataLabel>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
@@ -299,6 +319,11 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<JobEmployeeAssignments job={job} />
|
<JobEmployeeAssignments job={job} />
|
||||||
|
<Divider style={{ margin: ".5rem" }} />
|
||||||
|
<DataLabel label={t("jobs.labels.labor_hrs")}>
|
||||||
|
{bodyHrs.toFixed(1)} / {refinishHrs.toFixed(1)} /{" "}
|
||||||
|
{(bodyHrs + refinishHrs).toFixed(1)}
|
||||||
|
</DataLabel>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
|
|||||||
const handleTableChange = (pagination, filters, sorter) => {
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.ro_number"),
|
title: t("jobs.fields.ro_number"),
|
||||||
@@ -44,6 +44,15 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
|
|||||||
title: t("jobs.fields.vehicle"),
|
title: t("jobs.fields.vehicle"),
|
||||||
dataIndex: "vehicleid",
|
dataIndex: "vehicleid",
|
||||||
key: "vehicleid",
|
key: "vehicleid",
|
||||||
|
sorter: (a, b) =>
|
||||||
|
alphaSort(
|
||||||
|
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${
|
||||||
|
a.v_model_desc || ""
|
||||||
|
}`,
|
||||||
|
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
|
||||||
|
),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "vehicleid" && state.sortedInfo.order,
|
||||||
render: (text, record) =>
|
render: (text, record) =>
|
||||||
record.vehicleid ? (
|
record.vehicleid ? (
|
||||||
<Link to={`/manage/vehicles/${record.vehicleid}`}>
|
<Link to={`/manage/vehicles/${record.vehicleid}`}>
|
||||||
@@ -67,9 +76,15 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
|
|||||||
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),
|
sorter: (a, b) =>
|
||||||
|
statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||||
|
filters: bodyshop.md_ro_statuses.statuses.map((status) => ({
|
||||||
|
text: status,
|
||||||
|
value: status,
|
||||||
|
})),
|
||||||
|
onFilter: (value, record) => value.includes(record.status),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import { DeleteFilled, WarningFilled, DownOutlined } from "@ant-design/icons";
|
import { DeleteFilled, DownOutlined, WarningFilled } from "@ant-design/icons";
|
||||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||||
import {
|
import {
|
||||||
|
Checkbox,
|
||||||
Divider,
|
Divider,
|
||||||
|
Dropdown,
|
||||||
Form,
|
Form,
|
||||||
Input,
|
Input,
|
||||||
InputNumber,
|
InputNumber,
|
||||||
|
Menu,
|
||||||
Radio,
|
Radio,
|
||||||
|
Select,
|
||||||
Space,
|
Space,
|
||||||
Tag,
|
Tag,
|
||||||
Select,
|
|
||||||
Menu,
|
|
||||||
Dropdown,
|
|
||||||
Checkbox,
|
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -255,7 +255,7 @@ export function PartsOrderModalComponent({
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<InputNumber />
|
<InputNumber min={1} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("parts_orders.fields.act_price")}
|
label={t("parts_orders.fields.act_price")}
|
||||||
|
|||||||
@@ -95,15 +95,13 @@ export function PartsQueueListComponent({ bodyshop }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleOnRowClick = (record) => {
|
const handleOnRowClick = (record) => {
|
||||||
if (record) {
|
if (record?.id) {
|
||||||
if (record.id) {
|
history.replace({
|
||||||
history.push({
|
search: queryString.stringify({
|
||||||
search: queryString.stringify({
|
...searchParams,
|
||||||
...searchParams,
|
selected: record.id,
|
||||||
selected: record.id,
|
}),
|
||||||
}),
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -350,6 +348,13 @@ export function PartsQueueListComponent({ bodyshop }) {
|
|||||||
selectedRowKeys: [selected],
|
selectedRowKeys: [selected],
|
||||||
type: "radio",
|
type: "radio",
|
||||||
}}
|
}}
|
||||||
|
onRow={(record, rowIndex) => {
|
||||||
|
return {
|
||||||
|
onClick: (event) => {
|
||||||
|
handleOnRowClick(record);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -139,8 +139,8 @@ const PaymentExpandedRowComponent = ({ record, bodyshop }) => {
|
|||||||
contentStyle={{ fontWeight: "600" }}
|
contentStyle={{ fontWeight: "600" }}
|
||||||
column={4}
|
column={4}
|
||||||
>
|
>
|
||||||
<Descriptions.Item label={t("job_payments.titles.payer")}>
|
<Descriptions.Item label={t("job_payments.titles.hint")}>
|
||||||
{record.payer}
|
{payment_response?.response?.methodhint}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label={t("job_payments.titles.payername")}>
|
<Descriptions.Item label={t("job_payments.titles.payername")}>
|
||||||
{payment_response?.response?.nameOnCard ?? ""}
|
{payment_response?.response?.nameOnCard ?? ""}
|
||||||
@@ -155,7 +155,7 @@ const PaymentExpandedRowComponent = ({ record, bodyshop }) => {
|
|||||||
{record.transactionid}
|
{record.transactionid}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label={t("job_payments.titles.paymentid")}>
|
<Descriptions.Item label={t("job_payments.titles.paymentid")}>
|
||||||
{payment_response?.response?.paymentreferenceid ?? ""}
|
{payment_response?.ext_paymentid ?? ""}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label={t("job_payments.titles.paymenttype")}>
|
<Descriptions.Item label={t("job_payments.titles.paymenttype")}>
|
||||||
{record.type}
|
{record.type}
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import React from "react";
|
|
||||||
import { Button, notification } from "antd";
|
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
|
import { Button, notification } from "antd";
|
||||||
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { UPDATE_PAYMENT } from "../../graphql/payments.queries";
|
|
||||||
import { selectCurrentUser } from "../../redux/user/user.selectors";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||||
|
import { UPDATE_PAYMENT } from "../../graphql/payments.queries";
|
||||||
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
|
import { selectPayment } from "../../redux/modals/modals.selectors";
|
||||||
|
import { selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
|
paymentModal: selectPayment,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
@@ -24,6 +26,7 @@ const PaymentMarkForExportButton = ({
|
|||||||
refetch,
|
refetch,
|
||||||
setPaymentContext,
|
setPaymentContext,
|
||||||
currentUser,
|
currentUser,
|
||||||
|
paymentModal,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [insertExportLog, { loading: exportLogLoading }] =
|
const [insertExportLog, { loading: exportLogLoading }] =
|
||||||
@@ -46,7 +49,6 @@ const PaymentMarkForExportButton = ({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const paymentUpdateResponse = await updatePayment({
|
const paymentUpdateResponse = await updatePayment({
|
||||||
variables: {
|
variables: {
|
||||||
paymentId: payment.id,
|
paymentId: payment.id,
|
||||||
@@ -55,25 +57,33 @@ const PaymentMarkForExportButton = ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!!!paymentUpdateResponse.errors) {
|
if (!!!paymentUpdateResponse.errors) {
|
||||||
notification.open({
|
notification.open({
|
||||||
type: "success",
|
type: "success",
|
||||||
key: "paymentsuccessmarkforexport",
|
key: "paymentsuccessmarkforexport",
|
||||||
message: t("payments.successes.markexported"),
|
message: t("payments.successes.markexported"),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (refetch) refetch();
|
|
||||||
|
|
||||||
setPaymentContext({
|
setPaymentContext({
|
||||||
actions: {
|
actions: {
|
||||||
refetch,
|
refetch,
|
||||||
},
|
},
|
||||||
context: {
|
context: {
|
||||||
|
...paymentModal.context,
|
||||||
...payment,
|
...payment,
|
||||||
exportedat: today,
|
exportedat: today,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (refetch) {
|
||||||
|
if (paymentModal.context.refetchRequiresContext) {
|
||||||
|
refetch(
|
||||||
|
paymentUpdateResponse &&
|
||||||
|
paymentUpdateResponse.data.update_payments.returning[0]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
message: t("payments.errors.exporting", {
|
message: t("payments.errors.exporting", {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
|
|
||||||
import { Button, Form, Modal, notification, Space } from "antd";
|
import { Button, Form, Modal, notification, Space } from "antd";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -19,8 +18,8 @@ import {
|
|||||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
import PaymentForm from "../payment-form/payment-form.component";
|
import PaymentForm from "../payment-form/payment-form.component";
|
||||||
import PaymentReexportButton from "../payment-reexport-button/payment-reexport-button.component";
|
|
||||||
import PaymentMarkForExportButton from "../payment-mark-export-button/payment-mark-export-button-component";
|
import PaymentMarkForExportButton from "../payment-mark-export-button/payment-mark-export-button-component";
|
||||||
|
import PaymentReexportButton from "../payment-reexport-button/payment-reexport-button.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
paymentModal: selectPayment,
|
paymentModal: selectPayment,
|
||||||
@@ -97,16 +96,21 @@ function PaymentModalContainer({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!!!updatedPayment.errors) {
|
if (!!!updatedPayment.errors) {
|
||||||
notification["success"]({ message: t("payments.successes.payment") });
|
notification["success"]({ message: t("payments.successes.paymentupdate") });
|
||||||
} else {
|
} else {
|
||||||
notification["error"]({ message: t("payments.errors.payment") });
|
notification["error"]({ message: t("payments.errors.paymentupdate") });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actions.refetch)
|
if (actions.refetch) {
|
||||||
actions.refetch(
|
if (context.refetchRequiresContext) {
|
||||||
updatedPayment && updatedPayment.data.update_payments.returning[0]
|
actions.refetch(
|
||||||
);
|
updatedPayment && updatedPayment.data.update_payments.returning[0]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
actions.refetch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (enterAgain) {
|
if (enterAgain) {
|
||||||
const prev = form.getFieldsValue(["date"]);
|
const prev = form.getFieldsValue(["date"]);
|
||||||
@@ -159,7 +163,7 @@ function PaymentModalContainer({
|
|||||||
}}
|
}}
|
||||||
afterClose={() => form.resetFields()}
|
afterClose={() => form.resetFields()}
|
||||||
footer={
|
footer={
|
||||||
<span>
|
<Space>
|
||||||
<Button onClick={handleCancel}>{t("general.actions.cancel")}</Button>
|
<Button onClick={handleCancel}>{t("general.actions.cancel")}</Button>
|
||||||
<Button loading={loading} onClick={() => form.submit()}>
|
<Button loading={loading} onClick={() => form.submit()}>
|
||||||
{t("general.actions.save")}
|
{t("general.actions.save")}
|
||||||
@@ -175,7 +179,7 @@ function PaymentModalContainer({
|
|||||||
{t("general.actions.saveandnew")}
|
{t("general.actions.saveandnew")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</span>
|
</Space>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{!context || (context && !context.id) ? null : (
|
{!context || (context && !context.id) ? null : (
|
||||||
@@ -194,7 +198,6 @@ function PaymentModalContainer({
|
|||||||
autoComplete={"off"}
|
autoComplete={"off"}
|
||||||
form={form}
|
form={form}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
initialValues={context || {}}
|
|
||||||
disabled={context?.exportedat}
|
disabled={context?.exportedat}
|
||||||
>
|
>
|
||||||
<PaymentForm form={form} />
|
<PaymentForm form={form} />
|
||||||
|
|||||||
@@ -1,17 +1,27 @@
|
|||||||
import React from "react";
|
|
||||||
import { Button, notification } from "antd";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { UPDATE_PAYMENT } from "../../graphql/payments.queries";
|
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { Button, notification } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { UPDATE_PAYMENT } from "../../graphql/payments.queries";
|
||||||
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
|
import { selectPayment } from "../../redux/modals/modals.selectors";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
paymentModal: selectPayment,
|
||||||
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setPaymentContext: (context) =>
|
setPaymentContext: (context) =>
|
||||||
dispatch(setModalContext({ context: context, modal: "payment" })),
|
dispatch(setModalContext({ context: context, modal: "payment" })),
|
||||||
});
|
});
|
||||||
|
|
||||||
const PaymentReexportButton = ({ payment, refetch, setPaymentContext }) => {
|
const PaymentReexportButton = ({
|
||||||
|
paymentModal,
|
||||||
|
payment,
|
||||||
|
refetch,
|
||||||
|
setPaymentContext,
|
||||||
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [updatePayment, { loading }] = useMutation(UPDATE_PAYMENT);
|
const [updatePayment, { loading }] = useMutation(UPDATE_PAYMENT);
|
||||||
|
|
||||||
@@ -24,25 +34,32 @@ const PaymentReexportButton = ({ payment, refetch, setPaymentContext }) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!!!paymentUpdateResponse.errors) {
|
if (!!!paymentUpdateResponse.errors) {
|
||||||
notification.open({
|
notification.open({
|
||||||
type: "success",
|
type: "success",
|
||||||
key: "paymentsuccessexport",
|
key: "paymentsuccessexport",
|
||||||
message: t("payments.successes.markreexported"),
|
message: t("payments.successes.markreexported"),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (refetch) refetch();
|
|
||||||
|
|
||||||
setPaymentContext({
|
setPaymentContext({
|
||||||
actions: {
|
actions: {
|
||||||
refetch,
|
refetch,
|
||||||
},
|
},
|
||||||
context: {
|
context: {
|
||||||
|
...paymentModal.context,
|
||||||
...payment,
|
...payment,
|
||||||
exportedat: null,
|
exportedat: null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
if (refetch) {
|
||||||
|
if (paymentModal.context.refetchRequiresContext) {
|
||||||
|
refetch(
|
||||||
|
paymentUpdateResponse &&
|
||||||
|
paymentUpdateResponse.data.update_payments.returning[0]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
message: t("payments.errors.exporting", {
|
message: t("payments.errors.exporting", {
|
||||||
@@ -63,4 +80,7 @@ const PaymentReexportButton = ({ payment, refetch, setPaymentContext }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps)(PaymentReexportButton);
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(PaymentReexportButton);
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
|||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
|
import { pageLimit } from "../../utils/config";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
import CaBcEtfTableModalContainer from "../ca-bc-etf-table-modal/ca-bc-etf-table-modal.container";
|
import CaBcEtfTableModalContainer from "../ca-bc-etf-table-modal/ca-bc-etf-table-modal.container";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
||||||
import {pageLimit} from "../../utils/config";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
@@ -184,7 +184,10 @@ export function PaymentsListPaginated({
|
|||||||
}
|
}
|
||||||
: refetch,
|
: refetch,
|
||||||
},
|
},
|
||||||
context: apolloResults ? apolloResults : record,
|
context: {
|
||||||
|
...(apolloResults ? apolloResults : record),
|
||||||
|
refetchRequiresContext: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
notification,
|
notification,
|
||||||
Popover,
|
Popover,
|
||||||
Radio,
|
Radio,
|
||||||
|
Space,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -115,10 +116,16 @@ export function PrintCenterJobsLabels({ bodyshop, jobId }) {
|
|||||||
>
|
>
|
||||||
<InputNumber min={1} precision={0} max={99} />
|
<InputNumber min={1} precision={0} max={99} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Button type="primary" loading={loading} onClick={handleOk}>
|
<div style={{ display: "flex", justifyContent: "flex-end" }}>
|
||||||
{t("general.actions.print")}
|
<Space>
|
||||||
</Button>
|
<Button type="primary" loading={loading} onClick={handleOk}>
|
||||||
<Button onClick={handleCancel}>{t("general.actions.cancel")}</Button>
|
{t("general.actions.print")}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleCancel}>
|
||||||
|
{t("general.actions.cancel")}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ const sortByParentId = (arr) => {
|
|||||||
//console.log("sortByParentId -> byParentsIdsList", byParentsIdsList);
|
//console.log("sortByParentId -> byParentsIdsList", byParentsIdsList);
|
||||||
|
|
||||||
while (byParentsIdsList[parentId]) {
|
while (byParentsIdsList[parentId]) {
|
||||||
sortedList.push(byParentsIdsList[parentId][0]);
|
sortedList.push(...byParentsIdsList[parentId]); //Spread in the whole list in case several items have the same parents.
|
||||||
parentId = byParentsIdsList[parentId][0].id;
|
parentId = byParentsIdsList[parentId][byParentsIdsList[parentId].length -1].id; //Grab the ID from the last one.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (byParentsIdsList["null"])
|
if (byParentsIdsList["null"])
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { BranchesOutlined, PauseCircleOutlined } from "@ant-design/icons";
|
import { BranchesOutlined, PauseCircleOutlined } from "@ant-design/icons";
|
||||||
import { Checkbox, Space, Tooltip } from "antd";
|
import { Checkbox, Space, Tooltip } from "antd";
|
||||||
import i18n from "i18next";
|
import i18n from "i18next";
|
||||||
import moment from "moment";
|
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { TimeFormatter } from "../../utils/DateFormatter";
|
import { TimeFormatter } from "../../utils/DateFormatter";
|
||||||
@@ -190,17 +189,12 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
|
|||||||
state.sortedInfo.columnKey === "date_next_contact" &&
|
state.sortedInfo.columnKey === "date_next_contact" &&
|
||||||
state.sortedInfo.order,
|
state.sortedInfo.order,
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<span
|
<ProductionListDate
|
||||||
style={{
|
record={record}
|
||||||
color:
|
field="date_next_contact"
|
||||||
record.date_next_contact &&
|
pastIndicator
|
||||||
moment(record.date_next_contact).isBefore(moment())
|
time
|
||||||
? "red"
|
/>
|
||||||
: "",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ProductionListDate record={record} field="date_next_contact" time />
|
|
||||||
</span>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -308,7 +302,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
|
|||||||
onFilter: (value, record) =>
|
onFilter: (value, record) =>
|
||||||
value.includes(record.special_coverage_policy),
|
value.includes(record.special_coverage_policy),
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Checkbox disabled checked={record.special_coverage_policy} />
|
<Checkbox checked={record.special_coverage_policy} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Icon from "@ant-design/icons";
|
import Icon from "@ant-design/icons";
|
||||||
import { Popover } from "antd";
|
import { Popover, Space } from "antd";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
@@ -69,19 +69,22 @@ export function ScheduleCalendarHeaderComponent({
|
|||||||
{loadData && loadData.allJobsOut ? (
|
{loadData && loadData.allJobsOut ? (
|
||||||
loadData.allJobsOut.map((j) => (
|
loadData.allJobsOut.map((j) => (
|
||||||
<tr key={j.id}>
|
<tr key={j.id}>
|
||||||
<td>
|
<td style={{ padding: "2.5px" }}>
|
||||||
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
|
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link> (
|
||||||
|
{j.status})
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td style={{ padding: "2.5px" }}>
|
||||||
<OwnerNameDisplay ownerObject={j} />
|
<OwnerNameDisplay ownerObject={j} />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td style={{ padding: "2.5px" }}>
|
||||||
{`(${(
|
{`(${j.labhrs?.aggregate?.sum?.mod_lb_hrs?.toFixed(1) || 0}/${
|
||||||
j.labhrs.aggregate.sum.mod_lb_hrs +
|
j.larhrs?.aggregate?.sum?.mod_lb_hrs?.toFixed(1) || 0
|
||||||
j.larhrs.aggregate.sum.mod_lb_hrs
|
}/${(
|
||||||
|
j.labhrs.aggregate?.sum?.mod_lb_hrs +
|
||||||
|
j.larhrs.aggregate?.sum?.mod_lb_hrs
|
||||||
).toFixed(1)} ${t("general.labels.hours")})`}
|
).toFixed(1)} ${t("general.labels.hours")})`}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td style={{ padding: "2.5px" }}>
|
||||||
<DateTimeFormatter>
|
<DateTimeFormatter>
|
||||||
{j.scheduled_completion}
|
{j.scheduled_completion}
|
||||||
</DateTimeFormatter>
|
</DateTimeFormatter>
|
||||||
@@ -90,7 +93,9 @@ export function ScheduleCalendarHeaderComponent({
|
|||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<tr>
|
<tr>
|
||||||
<td>{t("appointments.labels.nocompletingjobs")}</td>
|
<td style={{ padding: "2.5px" }}>
|
||||||
|
{t("appointments.labels.nocompletingjobs")}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -105,27 +110,30 @@ export function ScheduleCalendarHeaderComponent({
|
|||||||
{loadData && loadData.allJobsIn ? (
|
{loadData && loadData.allJobsIn ? (
|
||||||
loadData.allJobsIn.map((j) => (
|
loadData.allJobsIn.map((j) => (
|
||||||
<tr key={j.id}>
|
<tr key={j.id}>
|
||||||
<td>
|
<td style={{ padding: "2.5px" }}>
|
||||||
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
|
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
|
||||||
{j.status}
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td style={{ padding: "2.5px" }}>
|
||||||
<OwnerNameDisplay ownerObject={j} />
|
<OwnerNameDisplay ownerObject={j} />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td style={{ padding: "2.5px" }}>
|
||||||
{`(${(
|
{`(${j.labhrs?.aggregate?.sum.mod_lb_hrs?.toFixed(1) || 0}/${
|
||||||
j.labhrs.aggregate.sum.mod_lb_hrs +
|
j.larhrs?.aggregate?.sum?.mod_lb_hrs?.toFixed(1) || 0
|
||||||
j.larhrs.aggregate.sum.mod_lb_hrs
|
}/${(
|
||||||
|
j.labhrs?.aggregate?.sum?.mod_lb_hrs +
|
||||||
|
j.larhrs?.aggregate?.sum?.mod_lb_hrs
|
||||||
).toFixed(1)} ${t("general.labels.hours")})`}
|
).toFixed(1)} ${t("general.labels.hours")})`}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td style={{ padding: "2.5px" }}>
|
||||||
<DateTimeFormatter>{j.scheduled_in}</DateTimeFormatter>
|
<DateTimeFormatter>{j.scheduled_in}</DateTimeFormatter>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<tr>
|
<tr>
|
||||||
<td>{t("appointments.labels.noarrivingjobs")}</td>
|
<td style={{ padding: "2.5px" }}>
|
||||||
|
{t("appointments.labels.noarrivingjobs")}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -136,25 +144,32 @@ export function ScheduleCalendarHeaderComponent({
|
|||||||
const LoadComponent = loadData ? (
|
const LoadComponent = loadData ? (
|
||||||
<div>
|
<div>
|
||||||
<div className="imex-flex-row imex-flex-row__flex-space-around">
|
<div className="imex-flex-row imex-flex-row__flex-space-around">
|
||||||
<Popover
|
<Space>
|
||||||
placement={"bottom"}
|
<Popover
|
||||||
content={jobsInPopup}
|
placement={"bottom"}
|
||||||
trigger="hover"
|
content={jobsInPopup}
|
||||||
title={t("appointments.labels.arrivingjobs")}
|
trigger="hover"
|
||||||
>
|
title={t("appointments.labels.arrivingjobs")}
|
||||||
<Icon component={MdFileDownload} style={{ color: "green" }} />
|
>
|
||||||
{(loadData.allHoursIn || 0) && loadData.allHoursIn.toFixed(2)}
|
<Icon component={MdFileDownload} style={{ color: "green" }} />
|
||||||
</Popover>
|
{(loadData.allHoursInBody || 0) &&
|
||||||
<Popover
|
loadData.allHoursInBody.toFixed(1)}
|
||||||
placement={"bottom"}
|
/
|
||||||
content={jobsOutPopup}
|
{(loadData.allHoursInRefinish || 0) &&
|
||||||
trigger="hover"
|
loadData.allHoursInRefinish.toFixed(1)}
|
||||||
title={t("appointments.labels.completingjobs")}
|
/{(loadData.allHoursIn || 0) && loadData.allHoursIn.toFixed(1)}
|
||||||
>
|
</Popover>
|
||||||
<Icon component={MdFileUpload} style={{ color: "red" }} />
|
<Popover
|
||||||
{(loadData.allHoursOut || 0) && loadData.allHoursOut.toFixed(2)}
|
placement={"bottom"}
|
||||||
</Popover>
|
content={jobsOutPopup}
|
||||||
<ScheduleCalendarHeaderGraph loadData={loadData} />
|
trigger="hover"
|
||||||
|
title={t("appointments.labels.completingjobs")}
|
||||||
|
>
|
||||||
|
<Icon component={MdFileUpload} style={{ color: "red" }} />
|
||||||
|
{(loadData.allHoursOut || 0) && loadData.allHoursOut.toFixed(1)}
|
||||||
|
</Popover>
|
||||||
|
<ScheduleCalendarHeaderGraph loadData={loadData} />
|
||||||
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ul style={{ listStyleType: "none", columns: "2 auto", padding: 0 }}>
|
<ul style={{ listStyleType: "none", columns: "2 auto", padding: 0 }}>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
InputNumber,
|
InputNumber,
|
||||||
Select,
|
Select,
|
||||||
|
Space,
|
||||||
Switch,
|
Switch,
|
||||||
Typography,
|
Typography,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
@@ -17,6 +18,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 FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
||||||
|
|
||||||
const SelectorDiv = styled.div`
|
const SelectorDiv = styled.div`
|
||||||
.ant-form-item .ant-select {
|
.ant-form-item .ant-select {
|
||||||
@@ -191,7 +193,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<LayoutFormRow header={t("bodyshop.labels.dms.cdk.payers")}>
|
<LayoutFormRow header={t("bodyshop.labels.dms.cdk.payers")}>
|
||||||
<Form.List name={["cdk_configuration", "payers"]}>
|
<Form.List name={["cdk_configuration", "payers"]}>
|
||||||
{(fields, { add, remove }) => {
|
{(fields, { add, remove, move }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
@@ -249,11 +251,18 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<DeleteFilled
|
<Space align="center">
|
||||||
onClick={() => {
|
<DeleteFilled
|
||||||
remove(field.name);
|
onClick={() => {
|
||||||
}}
|
remove(field.name);
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
<FormListMoveArrows
|
||||||
|
move={move}
|
||||||
|
index={index}
|
||||||
|
total={fields.length}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
))}
|
))}
|
||||||
@@ -345,7 +354,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
id="costs"
|
id="costs"
|
||||||
>
|
>
|
||||||
<Form.List name={["md_responsibility_centers", "costs"]}>
|
<Form.List name={["md_responsibility_centers", "costs"]}>
|
||||||
{(fields, { add, remove }) => {
|
{(fields, { add, remove, move }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
@@ -462,12 +471,18 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
<Input onBlur={handleBlur} />
|
<Input onBlur={handleBlur} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
<Space align="center">
|
||||||
<DeleteFilled
|
<DeleteFilled
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
remove(field.name);
|
remove(field.name);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<FormListMoveArrows
|
||||||
|
move={move}
|
||||||
|
index={index}
|
||||||
|
total={fields.length}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
))}
|
))}
|
||||||
@@ -493,7 +508,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
id="profits"
|
id="profits"
|
||||||
>
|
>
|
||||||
<Form.List name={["md_responsibility_centers", "profits"]}>
|
<Form.List name={["md_responsibility_centers", "profits"]}>
|
||||||
{(fields, { add, remove }) => {
|
{(fields, { add, remove, move }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
@@ -595,11 +610,18 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
<Input onBlur={handleBlur} />
|
<Input onBlur={handleBlur} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
<DeleteFilled
|
<Space align="center">
|
||||||
onClick={() => {
|
<DeleteFilled
|
||||||
remove(field.name);
|
onClick={() => {
|
||||||
}}
|
remove(field.name);
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
<FormListMoveArrows
|
||||||
|
move={move}
|
||||||
|
index={index}
|
||||||
|
total={fields.length}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -39,8 +39,23 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
form.getFieldValue(["md_ro_statuses", "statuses"]) || []
|
form.getFieldValue(["md_ro_statuses", "statuses"]) || []
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [productionStatus, setProductionStatus] = useState(
|
||||||
|
(
|
||||||
|
form.getFieldValue(["md_ro_statuses", "production_statuses"]) || []
|
||||||
|
).concat(
|
||||||
|
form.getFieldValue(["md_ro_statuses", "additional_board_statuses"]) || []
|
||||||
|
) || []
|
||||||
|
);
|
||||||
|
|
||||||
const handleBlur = () => {
|
const handleBlur = () => {
|
||||||
setOptions(form.getFieldValue(["md_ro_statuses", "statuses"]));
|
setOptions(form.getFieldValue(["md_ro_statuses", "statuses"]));
|
||||||
|
setProductionStatus(
|
||||||
|
form
|
||||||
|
.getFieldValue(["md_ro_statuses", "production_statuses"])
|
||||||
|
.concat(
|
||||||
|
form.getFieldValue(["md_ro_statuses", "additional_board_statuses"])
|
||||||
|
)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -346,7 +361,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select>
|
||||||
{options.map((item, idx) => (
|
{productionStatus.map((item, idx) => (
|
||||||
<Select.Option key={idx} value={item}>
|
<Select.Option key={idx} value={item}>
|
||||||
{item}
|
{item}
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ 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 { alphaSort, statusSort } from "../../utils/sorters";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay, {
|
||||||
|
OwnerNameDisplayFunction,
|
||||||
|
} from "../owner-name-display/owner-name-display.component";
|
||||||
import VehicleDetailUpdateJobsComponent from "../vehicle-detail-update-jobs/vehicle-detail-update-jobs.component";
|
import VehicleDetailUpdateJobsComponent from "../vehicle-detail-update-jobs/vehicle-detail-update-jobs.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
@@ -45,6 +47,10 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
|
|||||||
title: t("jobs.fields.owner"),
|
title: t("jobs.fields.owner"),
|
||||||
dataIndex: "owner",
|
dataIndex: "owner",
|
||||||
key: "owner",
|
key: "owner",
|
||||||
|
sorter: (a, b) =>
|
||||||
|
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Link to={`/manage/owners/${record.owner.id}`}>
|
<Link to={`/manage/owners/${record.owner.id}`}>
|
||||||
<OwnerNameDisplay ownerObject={record} />
|
<OwnerNameDisplay ownerObject={record} />
|
||||||
@@ -63,9 +69,15 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
|
|||||||
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),
|
sorter: (a, b) =>
|
||||||
|
statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||||
|
filters: bodyshop.md_ro_statuses.statuses.map((status) => ({
|
||||||
|
text: status,
|
||||||
|
value: status,
|
||||||
|
})),
|
||||||
|
onFilter: (value, record) => value.includes(record.status),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import queryString from "query-string";
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||||
|
import { pageLimit } from "../../utils/config";
|
||||||
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
||||||
import {pageLimit} from "../../utils/config";
|
import { alphaSort } from './../../utils/sorters';
|
||||||
export default function VehiclesListComponent({
|
export default function VehiclesListComponent({
|
||||||
loading,
|
loading,
|
||||||
vehicles,
|
vehicles,
|
||||||
@@ -31,6 +32,8 @@ export default function VehiclesListComponent({
|
|||||||
title: t("vehicles.fields.v_vin"),
|
title: t("vehicles.fields.v_vin"),
|
||||||
dataIndex: "v_vin",
|
dataIndex: "v_vin",
|
||||||
key: "v_vin",
|
key: "v_vin",
|
||||||
|
sorter: (a, b) => alphaSort(a.v_vin, b.v_vin),
|
||||||
|
sortOrder: state.sortedInfo.columnKey === "v_vin" && state.sortedInfo.order,
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Link to={"/manage/vehicles/" + record.id}>
|
<Link to={"/manage/vehicles/" + record.id}>
|
||||||
<VehicleVinDisplay>{record.v_vin || "N/A"}</VehicleVinDisplay>
|
<VehicleVinDisplay>{record.v_vin || "N/A"}</VehicleVinDisplay>
|
||||||
@@ -51,8 +54,10 @@ export default function VehiclesListComponent({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("vehicles.fields.plate_no"),
|
title: t("vehicles.fields.plate_no"),
|
||||||
dataIndex: "plate",
|
dataIndex: "plate_no",
|
||||||
key: "plate",
|
key: "plate_no",
|
||||||
|
sorter: (a, b) => alphaSort(a.plate_no, b.plate_no),
|
||||||
|
sortOrder: state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return (
|
return (
|
||||||
<span>{`${record.plate_st || ""} | ${record.plate_no || ""}`}</span>
|
<span>{`${record.plate_st || ""} | ${record.plate_no || ""}`}</span>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export const QUERY_AVAILABLE_CC = gql`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const CHECK_CC_FLEET_NUMBER = gql`
|
export const CHECK_CC_FLEET_NUMBER = gql`
|
||||||
query CHECK_VENDOR_NAME($name: String!) {
|
query CHECK_CC_FLEET_NUMBER($name: String!) {
|
||||||
courtesycars_aggregate(where: { fleetnumber: { _ilike: $name } }) {
|
courtesycars_aggregate(where: { fleetnumber: { _ilike: $name } }) {
|
||||||
aggregate {
|
aggregate {
|
||||||
count
|
count
|
||||||
|
|||||||
@@ -172,6 +172,12 @@ export const UPDATE_JOB_LINE = gql`
|
|||||||
id
|
id
|
||||||
notes
|
notes
|
||||||
mod_lbr_ty
|
mod_lbr_ty
|
||||||
|
mod_lb_hrs
|
||||||
|
part_type
|
||||||
|
op_code_desc
|
||||||
|
prt_dsmk_m
|
||||||
|
prt_dsmk_p
|
||||||
|
tax_part
|
||||||
part_qty
|
part_qty
|
||||||
db_price
|
db_price
|
||||||
act_price
|
act_price
|
||||||
|
|||||||
@@ -704,6 +704,7 @@ export const GET_JOB_BY_PK = gql`
|
|||||||
other_amount_payable
|
other_amount_payable
|
||||||
owner {
|
owner {
|
||||||
id
|
id
|
||||||
|
note
|
||||||
ownr_fn
|
ownr_fn
|
||||||
ownr_ln
|
ownr_ln
|
||||||
ownr_co_nm
|
ownr_co_nm
|
||||||
@@ -2204,6 +2205,8 @@ export const GET_JOB_LINE_ORDERS = gql`
|
|||||||
parts_order_lines(where: { job_line_id: { _eq: $joblineid } }) {
|
parts_order_lines(where: { job_line_id: { _eq: $joblineid } }) {
|
||||||
id
|
id
|
||||||
act_price
|
act_price
|
||||||
|
backordered_eta
|
||||||
|
backordered_on
|
||||||
parts_order {
|
parts_order {
|
||||||
id
|
id
|
||||||
order_date
|
order_date
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export const QUERY_VEHICLE_BY_ID = gql`
|
|||||||
jobs(order_by: { date_open: desc }) {
|
jobs(order_by: { date_open: desc }) {
|
||||||
id
|
id
|
||||||
ro_number
|
ro_number
|
||||||
|
ownr_co_nm
|
||||||
ownr_fn
|
ownr_fn
|
||||||
ownr_ln
|
ownr_ln
|
||||||
owner {
|
owner {
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import { setModalContext } from "../../redux/modals/modals.actions";
|
|||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
|
import { pageLimit } from "../../utils/config";
|
||||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||||
import {pageLimit} from "../../utils/config";
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setPartsOrderContext: (context) =>
|
setPartsOrderContext: (context) =>
|
||||||
@@ -125,9 +125,7 @@ export function BillsListPage({
|
|||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "is_credit_memo" &&
|
state.sortedInfo.columnKey === "is_credit_memo" &&
|
||||||
state.sortedInfo.order,
|
state.sortedInfo.order,
|
||||||
render: (text, record) => (
|
render: (text, record) => <Checkbox checked={record.is_credit_memo} />,
|
||||||
<Checkbox disabled checked={record.is_credit_memo} />
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("bills.fields.exported"),
|
title: t("bills.fields.exported"),
|
||||||
@@ -136,7 +134,7 @@ export function BillsListPage({
|
|||||||
sorter: (a, b) => a.exported - b.exported,
|
sorter: (a, b) => a.exported - b.exported,
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "exported" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "exported" && state.sortedInfo.order,
|
||||||
render: (text, record) => <Checkbox disabled checked={record.exported} />,
|
render: (text, record) => <Checkbox checked={record.exported} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("general.labels.actions"),
|
title: t("general.labels.actions"),
|
||||||
@@ -243,7 +241,7 @@ export function BillsListPage({
|
|||||||
extra={
|
extra={
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
{search.search && (
|
{search.search && (
|
||||||
<>
|
<Space align="center">
|
||||||
<Typography.Title level={4}>
|
<Typography.Title level={4}>
|
||||||
{t("general.labels.searchresults", { search: search.search })}
|
{t("general.labels.searchresults", { search: search.search })}
|
||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
@@ -256,7 +254,7 @@ export function BillsListPage({
|
|||||||
>
|
>
|
||||||
{t("general.actions.clear")}
|
{t("general.actions.clear")}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</Space>
|
||||||
)}
|
)}
|
||||||
<Button onClick={() => refetch()}>
|
<Button onClick={() => refetch()}>
|
||||||
<SyncOutlined />
|
<SyncOutlined />
|
||||||
|
|||||||
@@ -76,7 +76,11 @@ export function CourtesyCarCreateContainer({
|
|||||||
onFinish={handleFinish}
|
onFinish={handleFinish}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
>
|
>
|
||||||
<CourtesyCarFormComponent form={form} saveLoading={loading} />
|
<CourtesyCarFormComponent
|
||||||
|
form={form}
|
||||||
|
saveLoading={loading}
|
||||||
|
newCC={true}
|
||||||
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
</RbacWrapper>
|
</RbacWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -167,9 +167,7 @@ export function ExportLogsPageComponent({ bodyshop }) {
|
|||||||
{ text: "False", value: false },
|
{ text: "False", value: false },
|
||||||
],
|
],
|
||||||
onFilter: (value, record) => record.successful === value,
|
onFilter: (value, record) => record.successful === value,
|
||||||
render: (text, record) => (
|
render: (text, record) => <Checkbox checked={record.successful} />,
|
||||||
<Checkbox disabled checked={record.successful} />
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("general.labels.message"),
|
title: t("general.labels.message"),
|
||||||
|
|||||||
@@ -131,7 +131,12 @@ export function* calculateScheduleLoad({ payload: end }) {
|
|||||||
(load[itemDate].allHoursIn || 0) +
|
(load[itemDate].allHoursIn || 0) +
|
||||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||||
item.larhrs.aggregate.sum.mod_lb_hrs;
|
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||||
|
load[itemDate].allHoursInBody =
|
||||||
|
(load[itemDate].allHoursInBody || 0) +
|
||||||
|
item.labhrs.aggregate.sum.mod_lb_hrs;
|
||||||
|
load[itemDate].allHoursInRefinish =
|
||||||
|
(load[itemDate].allHoursInRefinish || 0) +
|
||||||
|
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||||
//If the job hasn't already arrived, add it to the jobs in list.
|
//If the job hasn't already arrived, add it to the jobs in list.
|
||||||
// Make sure it also hasn't already been completed, or isn't an in and out job.
|
// Make sure it also hasn't already been completed, or isn't an in and out job.
|
||||||
//This prevents the duplicate counting.
|
//This prevents the duplicate counting.
|
||||||
@@ -142,6 +147,12 @@ export function* calculateScheduleLoad({ payload: end }) {
|
|||||||
(load[itemDate].hoursIn || 0) +
|
(load[itemDate].hoursIn || 0) +
|
||||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||||
item.larhrs.aggregate.sum.mod_lb_hrs;
|
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||||
|
load[itemDate].hoursInBody =
|
||||||
|
(load[itemDate].hoursInBody || 0) +
|
||||||
|
item.labhrs.aggregate.sum.mod_lb_hrs;
|
||||||
|
load[itemDate].hoursInRefinish =
|
||||||
|
(load[itemDate].hoursInRefinish || 0) +
|
||||||
|
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
load[itemDate] = {
|
load[itemDate] = {
|
||||||
@@ -152,10 +163,18 @@ export function* calculateScheduleLoad({ payload: end }) {
|
|||||||
allHoursIn:
|
allHoursIn:
|
||||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||||
item.larhrs.aggregate.sum.mod_lb_hrs,
|
item.larhrs.aggregate.sum.mod_lb_hrs,
|
||||||
|
allHoursInBody: item.labhrs.aggregate.sum.mod_lb_hrs,
|
||||||
|
allHoursInRefinish: item.larhrs.aggregate.sum.mod_lb_hrs,
|
||||||
hoursIn: AddJobForSchedulingCalc
|
hoursIn: AddJobForSchedulingCalc
|
||||||
? item.labhrs.aggregate.sum.mod_lb_hrs +
|
? item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||||
item.larhrs.aggregate.sum.mod_lb_hrs
|
item.larhrs.aggregate.sum.mod_lb_hrs
|
||||||
: 0,
|
: 0,
|
||||||
|
hoursInBody: AddJobForSchedulingCalc
|
||||||
|
? item.labhrs.aggregate.sum.mod_lb_hrs
|
||||||
|
: 0,
|
||||||
|
hoursInRefinish: AddJobForSchedulingCalc
|
||||||
|
? item.larhrs.aggregate.sum.mod_lb_hrs
|
||||||
|
: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -179,6 +198,12 @@ export function* calculateScheduleLoad({ payload: end }) {
|
|||||||
(load[itemDate].allHoursOut || 0) +
|
(load[itemDate].allHoursOut || 0) +
|
||||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||||
item.larhrs.aggregate.sum.mod_lb_hrs;
|
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||||
|
load[itemDate].allHoursOutBody =
|
||||||
|
(load[itemDate].allHoursOutBody || 0) +
|
||||||
|
item.labhrs.aggregate.sum.mod_lb_hrs;
|
||||||
|
load[itemDate].allHoursOutRefinish =
|
||||||
|
(load[itemDate].allHoursOutRefinish || 0) +
|
||||||
|
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||||
//Add only the jobs that are still in production to get rid of.
|
//Add only the jobs that are still in production to get rid of.
|
||||||
//If it's not in production, we'd subtract unnecessarily.
|
//If it's not in production, we'd subtract unnecessarily.
|
||||||
load[itemDate].allJobsOut.push(item);
|
load[itemDate].allJobsOut.push(item);
|
||||||
@@ -189,6 +214,12 @@ export function* calculateScheduleLoad({ payload: end }) {
|
|||||||
(load[itemDate].hoursOut || 0) +
|
(load[itemDate].hoursOut || 0) +
|
||||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||||
item.larhrs.aggregate.sum.mod_lb_hrs;
|
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||||
|
load[itemDate].hoursOutBody =
|
||||||
|
(load[itemDate].hoursOutBody || 0) +
|
||||||
|
item.labhrs.aggregate.sum.mod_lb_hrs;
|
||||||
|
load[itemDate].hoursOutRefinish =
|
||||||
|
(load[itemDate].hoursOutRefinish || 0) +
|
||||||
|
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
load[itemDate] = {
|
load[itemDate] = {
|
||||||
@@ -201,6 +232,8 @@ export function* calculateScheduleLoad({ payload: end }) {
|
|||||||
allHoursOut:
|
allHoursOut:
|
||||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||||
item.larhrs.aggregate.sum.mod_lb_hrs,
|
item.larhrs.aggregate.sum.mod_lb_hrs,
|
||||||
|
allHoursOutBody: item.labhrs.aggregate.sum.mod_lb_hrs,
|
||||||
|
allHoursOutRefinish: item.larhrs.aggregate.sum.mod_lb_hrs,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -217,8 +217,9 @@
|
|||||||
"markexported": "Mark Exported",
|
"markexported": "Mark Exported",
|
||||||
"markforreexport": "Mark for Re-export",
|
"markforreexport": "Mark for Re-export",
|
||||||
"new": "New Bill",
|
"new": "New Bill",
|
||||||
|
"nobilllines": "This part has not yet been recieved.",
|
||||||
"noneselected": "No bill selected.",
|
"noneselected": "No bill selected.",
|
||||||
"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.",
|
||||||
"printlabels": "Print Labels",
|
"printlabels": "Print Labels",
|
||||||
"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.",
|
||||||
@@ -259,6 +260,7 @@
|
|||||||
"saving": "Error encountered while saving. {{message}}"
|
"saving": "Error encountered while saving. {{message}}"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"ReceivableCustomField": "QBO Receivable Custom Field {{number}}",
|
||||||
"address1": "Address 1",
|
"address1": "Address 1",
|
||||||
"address2": "Address 2",
|
"address2": "Address 2",
|
||||||
"appt_alt_transport": "Appointment Alternative Transportation Options",
|
"appt_alt_transport": "Appointment Alternative Transportation Options",
|
||||||
@@ -477,7 +479,6 @@
|
|||||||
"editaccess": "Users -> Edit access"
|
"editaccess": "Users -> Edit access"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ReceivableCustomField": "QBO Receivable Custom Field {{number}}",
|
|
||||||
"responsibilitycenter": "Responsibility Center",
|
"responsibilitycenter": "Responsibility Center",
|
||||||
"responsibilitycenter_accountdesc": "Account Description",
|
"responsibilitycenter_accountdesc": "Account Description",
|
||||||
"responsibilitycenter_accountitem": "Item",
|
"responsibilitycenter_accountitem": "Item",
|
||||||
@@ -608,7 +609,7 @@
|
|||||||
"dms": {
|
"dms": {
|
||||||
"cdk": {
|
"cdk": {
|
||||||
"controllist": "Control Number List",
|
"controllist": "Control Number List",
|
||||||
"payers": "CDK Payers"
|
"payers": "Payers"
|
||||||
},
|
},
|
||||||
"cdk_dealerid": "CDK Dealer ID",
|
"cdk_dealerid": "CDK Dealer ID",
|
||||||
"pbs_serialnumber": "PBS Serial Number",
|
"pbs_serialnumber": "PBS Serial Number",
|
||||||
@@ -844,8 +845,8 @@
|
|||||||
"notconfigured": "You do not have any current CSI Question Sets configured.",
|
"notconfigured": "You do not have any current CSI Question Sets configured.",
|
||||||
"notfoundsubtitle": "We were unable to find a survey using the link you provided. Please ensure the URL is correct or reach out to your shop for more help.",
|
"notfoundsubtitle": "We were unable to find a survey using the link you provided. Please ensure the URL is correct or reach out to your shop for more help.",
|
||||||
"notfoundtitle": "No survey found.",
|
"notfoundtitle": "No survey found.",
|
||||||
"surveycompletetitle": "Survey previously completed",
|
"surveycompletesubtitle": "This survey was already completed on {{date}}.",
|
||||||
"surveycompletesubtitle": "This survey was already completed on {{date}}."
|
"surveycompletetitle": "Survey previously completed"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"completedon": "Completed On",
|
"completedon": "Completed On",
|
||||||
@@ -854,13 +855,13 @@
|
|||||||
"validuntil": "Valid Until"
|
"validuntil": "Valid Until"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"copyright": "Copyright © $t(titles.app). All Rights Reserved.",
|
||||||
|
"greeting": "Hi {{name}}!",
|
||||||
|
"intro": "At {{shopname}}, we value your feedback. We would love to hear what you have to say. Please fill out the form below.",
|
||||||
"nologgedinuser": "Please log out of $t(titles.app)",
|
"nologgedinuser": "Please log out of $t(titles.app)",
|
||||||
"nologgedinuser_sub": "Users of $t(titles.app) cannot complete CSI surveys while logged in. Please log out and try again.",
|
"nologgedinuser_sub": "Users of $t(titles.app) cannot complete CSI surveys while logged in. Please log out and try again.",
|
||||||
"noneselected": "No response selected.",
|
"noneselected": "No response selected.",
|
||||||
"title": "Customer Satisfaction Survey",
|
"title": "Customer Satisfaction Survey"
|
||||||
"greeting": "Hi {{name}}!",
|
|
||||||
"intro": "At {{shopname}}, we value your feedback. We would love to hear what you have to say. Please fill out the form below.",
|
|
||||||
"copyright": "Copyright © $t(titles.app). All Rights Reserved."
|
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"created": "CSI created successfully.",
|
"created": "CSI created successfully.",
|
||||||
@@ -884,6 +885,7 @@
|
|||||||
"refhrs": "Refinish Hrs"
|
"refhrs": "Refinish Hrs"
|
||||||
},
|
},
|
||||||
"titles": {
|
"titles": {
|
||||||
|
"joblifecycle": "Job Lifecycle",
|
||||||
"labhours": "Total Body Hours",
|
"labhours": "Total Body Hours",
|
||||||
"larhours": "Total Refinish Hours",
|
"larhours": "Total Refinish Hours",
|
||||||
"monthlyemployeeefficiency": "Monthly Employee Efficiency",
|
"monthlyemployeeefficiency": "Monthly Employee Efficiency",
|
||||||
@@ -1113,6 +1115,7 @@
|
|||||||
"loadingshop": "Loading shop data...",
|
"loadingshop": "Loading shop data...",
|
||||||
"loggingin": "Authorizing...",
|
"loggingin": "Authorizing...",
|
||||||
"markedexported": "Manually marked as exported.",
|
"markedexported": "Manually marked as exported.",
|
||||||
|
"media": "Media",
|
||||||
"message": "Message",
|
"message": "Message",
|
||||||
"monday": "Monday",
|
"monday": "Monday",
|
||||||
"na": "N/A",
|
"na": "N/A",
|
||||||
@@ -1233,14 +1236,21 @@
|
|||||||
"columns": {
|
"columns": {
|
||||||
"duration": "Duration",
|
"duration": "Duration",
|
||||||
"end": "End",
|
"end": "End",
|
||||||
|
"human_readable": "Human Readable",
|
||||||
|
"percentage": "Percentage",
|
||||||
"relative_end": "Relative End",
|
"relative_end": "Relative End",
|
||||||
"relative_start": "Relative Start",
|
"relative_start": "Relative Start",
|
||||||
"start": "Start",
|
"start": "Start",
|
||||||
|
"status": "Status",
|
||||||
|
"status_count": "In Status",
|
||||||
"value": "Value"
|
"value": "Value"
|
||||||
},
|
},
|
||||||
"content": {
|
"content": {
|
||||||
|
"calculated_based_on": "Calculated based on",
|
||||||
"current_status_accumulated_time": "Current Status Accumulated Time",
|
"current_status_accumulated_time": "Current Status Accumulated Time",
|
||||||
"data_unavailable": " There is currently no Lifecycle data for this Job.",
|
"data_unavailable": " There is currently no Lifecycle data for this Job.",
|
||||||
|
"joblifecycle": "",
|
||||||
|
"jobs_in_since": "Jobs in since",
|
||||||
"legend_title": "Legend",
|
"legend_title": "Legend",
|
||||||
"loading": "Loading Job Timelines....",
|
"loading": "Loading Job Timelines....",
|
||||||
"not_available": "N/A",
|
"not_available": "N/A",
|
||||||
@@ -1252,6 +1262,10 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"fetch": "Error getting Job Lifecycle Data"
|
"fetch": "Error getting Job Lifecycle Data"
|
||||||
|
},
|
||||||
|
"titles": {
|
||||||
|
"dashboard": "Job Lifecycle",
|
||||||
|
"top_durations": "Top Durations"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"job_payments": {
|
"job_payments": {
|
||||||
@@ -1271,6 +1285,7 @@
|
|||||||
"amount": "Amount",
|
"amount": "Amount",
|
||||||
"dateOfPayment": "Date of Payment",
|
"dateOfPayment": "Date of Payment",
|
||||||
"descriptions": "Payment Details",
|
"descriptions": "Payment Details",
|
||||||
|
"hint": "Hint",
|
||||||
"payer": "Payer",
|
"payer": "Payer",
|
||||||
"payername": "Payer Name",
|
"payername": "Payer Name",
|
||||||
"paymentid": "Payment Reference ID",
|
"paymentid": "Payment Reference ID",
|
||||||
@@ -1561,7 +1576,7 @@
|
|||||||
"federal_tax_payable": "Federal Tax Payable",
|
"federal_tax_payable": "Federal Tax Payable",
|
||||||
"federal_tax_rate": "Federal Tax Rate",
|
"federal_tax_rate": "Federal Tax Rate",
|
||||||
"ins_addr1": "Insurance Co. Address",
|
"ins_addr1": "Insurance Co. Address",
|
||||||
"ins_city": "Insurance City",
|
"ins_city": "Insurance Co. City",
|
||||||
"ins_co_id": "Insurance Co. ID",
|
"ins_co_id": "Insurance Co. ID",
|
||||||
"ins_co_nm": "Insurance Company Name",
|
"ins_co_nm": "Insurance Company Name",
|
||||||
"ins_co_nm_short": "Ins. Co.",
|
"ins_co_nm_short": "Ins. Co.",
|
||||||
@@ -1823,6 +1838,7 @@
|
|||||||
"job": "Job Details",
|
"job": "Job Details",
|
||||||
"jobcosting": "Job Costing",
|
"jobcosting": "Job Costing",
|
||||||
"jobtotals": "Job Totals",
|
"jobtotals": "Job Totals",
|
||||||
|
"labor_hrs": "B/P/T Hrs",
|
||||||
"labor_rates_subtotal": "Labor Rates Subtotal",
|
"labor_rates_subtotal": "Labor Rates Subtotal",
|
||||||
"laborallocations": "Labor Allocations",
|
"laborallocations": "Labor Allocations",
|
||||||
"labortotals": "Labor Totals",
|
"labortotals": "Labor Totals",
|
||||||
@@ -2319,6 +2335,7 @@
|
|||||||
"markexported": "Payment(s) marked exported.",
|
"markexported": "Payment(s) marked exported.",
|
||||||
"markreexported": "Payment marked for re-export successfully",
|
"markreexported": "Payment marked for re-export successfully",
|
||||||
"payment": "Payment created successfully. ",
|
"payment": "Payment created successfully. ",
|
||||||
|
"paymentupdate": "Payment updated successfully. ",
|
||||||
"stripe": "Credit card transaction charged successfully."
|
"stripe": "Credit card transaction charged successfully."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2417,6 +2434,7 @@
|
|||||||
"invoice_total_payable": "Invoice (Total Payable)",
|
"invoice_total_payable": "Invoice (Total Payable)",
|
||||||
"iou_form": "IOU Form",
|
"iou_form": "IOU Form",
|
||||||
"job_costing_ro": "Job Costing",
|
"job_costing_ro": "Job Costing",
|
||||||
|
"job_lifecycle_ro": "Job Lifecycle",
|
||||||
"job_notes": "Job Notes",
|
"job_notes": "Job Notes",
|
||||||
"key_tag": "Key Tag",
|
"key_tag": "Key Tag",
|
||||||
"labels": {
|
"labels": {
|
||||||
@@ -2583,17 +2601,17 @@
|
|||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"advanced_filters": "Advanced Filters and Sorters",
|
"advanced_filters": "Advanced Filters and Sorters",
|
||||||
"advanced_filters_show": "Show",
|
|
||||||
"advanced_filters_hide": "Hide",
|
|
||||||
"advanced_filters_filters": "Filters",
|
|
||||||
"advanced_filters_sorters": "Sorters",
|
|
||||||
"advanced_filters_filter_field": "Field",
|
|
||||||
"advanced_filters_sorter_field": "Field",
|
|
||||||
"advanced_filters_true": "True",
|
|
||||||
"advanced_filters_false": "False",
|
"advanced_filters_false": "False",
|
||||||
"advanced_filters_sorter_direction": "Direction",
|
"advanced_filters_filter_field": "Field",
|
||||||
"advanced_filters_filter_operator": "Operator",
|
"advanced_filters_filter_operator": "Operator",
|
||||||
"advanced_filters_filter_value": "Value",
|
"advanced_filters_filter_value": "Value",
|
||||||
|
"advanced_filters_filters": "Filters",
|
||||||
|
"advanced_filters_hide": "Hide",
|
||||||
|
"advanced_filters_show": "Show",
|
||||||
|
"advanced_filters_sorter_direction": "Direction",
|
||||||
|
"advanced_filters_sorter_field": "Field",
|
||||||
|
"advanced_filters_sorters": "Sorters",
|
||||||
|
"advanced_filters_true": "True",
|
||||||
"dates": "Dates",
|
"dates": "Dates",
|
||||||
"employee": "Employee",
|
"employee": "Employee",
|
||||||
"filterson": "Filters on {{object}}: {{field}}",
|
"filterson": "Filters on {{object}}: {{field}}",
|
||||||
@@ -2675,6 +2693,8 @@
|
|||||||
"job_costing_ro_date_summary": "Job Costing by RO - Summary",
|
"job_costing_ro_date_summary": "Job Costing by RO - Summary",
|
||||||
"job_costing_ro_estimator": "Job Costing by Estimator",
|
"job_costing_ro_estimator": "Job Costing by Estimator",
|
||||||
"job_costing_ro_ins_co": "Job Costing by RO Source",
|
"job_costing_ro_ins_co": "Job Costing by RO Source",
|
||||||
|
"job_lifecycle_date_detail": "Job Lifecycle by Date - Detail",
|
||||||
|
"job_lifecycle_date_summary": "Job Lifecycle by Date - Summary",
|
||||||
"jobs_completed_not_invoiced": "Jobs Completed not Invoiced",
|
"jobs_completed_not_invoiced": "Jobs Completed not Invoiced",
|
||||||
"jobs_invoiced_not_exported": "Jobs Invoiced not Exported",
|
"jobs_invoiced_not_exported": "Jobs Invoiced not Exported",
|
||||||
"jobs_reconcile": "Parts/Sublet/Labor Reconciliation",
|
"jobs_reconcile": "Parts/Sublet/Labor Reconciliation",
|
||||||
|
|||||||
@@ -217,6 +217,7 @@
|
|||||||
"markexported": "",
|
"markexported": "",
|
||||||
"markforreexport": "",
|
"markforreexport": "",
|
||||||
"new": "",
|
"new": "",
|
||||||
|
"nobilllines": "",
|
||||||
"noneselected": "",
|
"noneselected": "",
|
||||||
"onlycmforinvoiced": "",
|
"onlycmforinvoiced": "",
|
||||||
"printlabels": "",
|
"printlabels": "",
|
||||||
@@ -259,6 +260,7 @@
|
|||||||
"saving": ""
|
"saving": ""
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"ReceivableCustomField": "",
|
||||||
"address1": "",
|
"address1": "",
|
||||||
"address2": "",
|
"address2": "",
|
||||||
"appt_alt_transport": "",
|
"appt_alt_transport": "",
|
||||||
@@ -477,7 +479,6 @@
|
|||||||
"editaccess": ""
|
"editaccess": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ReceivableCustomField": "",
|
|
||||||
"responsibilitycenter": "",
|
"responsibilitycenter": "",
|
||||||
"responsibilitycenter_accountdesc": "",
|
"responsibilitycenter_accountdesc": "",
|
||||||
"responsibilitycenter_accountitem": "",
|
"responsibilitycenter_accountitem": "",
|
||||||
@@ -844,8 +845,8 @@
|
|||||||
"notconfigured": "",
|
"notconfigured": "",
|
||||||
"notfoundsubtitle": "",
|
"notfoundsubtitle": "",
|
||||||
"notfoundtitle": "",
|
"notfoundtitle": "",
|
||||||
"surveycompletetitle": "",
|
"surveycompletesubtitle": "",
|
||||||
"surveycompletesubtitle": ""
|
"surveycompletetitle": ""
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"completedon": "",
|
"completedon": "",
|
||||||
@@ -854,13 +855,13 @@
|
|||||||
"validuntil": ""
|
"validuntil": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"copyright": "",
|
||||||
|
"greeting": "",
|
||||||
|
"intro": "",
|
||||||
"nologgedinuser": "",
|
"nologgedinuser": "",
|
||||||
"nologgedinuser_sub": "",
|
"nologgedinuser_sub": "",
|
||||||
"noneselected": "",
|
"noneselected": "",
|
||||||
"title": "",
|
"title": ""
|
||||||
"greeting": "",
|
|
||||||
"intro": "",
|
|
||||||
"copyright": ""
|
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"created": "",
|
"created": "",
|
||||||
@@ -884,6 +885,7 @@
|
|||||||
"refhrs": ""
|
"refhrs": ""
|
||||||
},
|
},
|
||||||
"titles": {
|
"titles": {
|
||||||
|
"joblifecycle": "",
|
||||||
"labhours": "",
|
"labhours": "",
|
||||||
"larhours": "",
|
"larhours": "",
|
||||||
"monthlyemployeeefficiency": "",
|
"monthlyemployeeefficiency": "",
|
||||||
@@ -1113,6 +1115,7 @@
|
|||||||
"loadingshop": "Cargando datos de la tienda ...",
|
"loadingshop": "Cargando datos de la tienda ...",
|
||||||
"loggingin": "Iniciando sesión ...",
|
"loggingin": "Iniciando sesión ...",
|
||||||
"markedexported": "",
|
"markedexported": "",
|
||||||
|
"media": "",
|
||||||
"message": "",
|
"message": "",
|
||||||
"monday": "",
|
"monday": "",
|
||||||
"na": "N / A",
|
"na": "N / A",
|
||||||
@@ -1233,14 +1236,21 @@
|
|||||||
"columns": {
|
"columns": {
|
||||||
"duration": "",
|
"duration": "",
|
||||||
"end": "",
|
"end": "",
|
||||||
|
"human_readable": "",
|
||||||
|
"percentage": "",
|
||||||
"relative_end": "",
|
"relative_end": "",
|
||||||
"relative_start": "",
|
"relative_start": "",
|
||||||
"start": "",
|
"start": "",
|
||||||
|
"status": "",
|
||||||
|
"status_count": "",
|
||||||
"value": ""
|
"value": ""
|
||||||
},
|
},
|
||||||
"content": {
|
"content": {
|
||||||
|
"calculated_based_on": "",
|
||||||
"current_status_accumulated_time": "",
|
"current_status_accumulated_time": "",
|
||||||
"data_unavailable": "",
|
"data_unavailable": "",
|
||||||
|
"joblifecycle": "",
|
||||||
|
"jobs_in_since": "",
|
||||||
"legend_title": "",
|
"legend_title": "",
|
||||||
"loading": "",
|
"loading": "",
|
||||||
"not_available": "",
|
"not_available": "",
|
||||||
@@ -1252,6 +1262,10 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"fetch": "Error al obtener los datos del ciclo de vida del trabajo"
|
"fetch": "Error al obtener los datos del ciclo de vida del trabajo"
|
||||||
|
},
|
||||||
|
"titles": {
|
||||||
|
"dashboard": "",
|
||||||
|
"top_durations": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"job_payments": {
|
"job_payments": {
|
||||||
@@ -1271,6 +1285,7 @@
|
|||||||
"amount": "",
|
"amount": "",
|
||||||
"dateOfPayment": "",
|
"dateOfPayment": "",
|
||||||
"descriptions": "",
|
"descriptions": "",
|
||||||
|
"hint": "",
|
||||||
"payer": "",
|
"payer": "",
|
||||||
"payername": "",
|
"payername": "",
|
||||||
"paymentid": "",
|
"paymentid": "",
|
||||||
@@ -1823,6 +1838,7 @@
|
|||||||
"job": "",
|
"job": "",
|
||||||
"jobcosting": "",
|
"jobcosting": "",
|
||||||
"jobtotals": "",
|
"jobtotals": "",
|
||||||
|
"labor_hrs": "",
|
||||||
"labor_rates_subtotal": "",
|
"labor_rates_subtotal": "",
|
||||||
"laborallocations": "",
|
"laborallocations": "",
|
||||||
"labortotals": "",
|
"labortotals": "",
|
||||||
@@ -2319,6 +2335,7 @@
|
|||||||
"markexported": "",
|
"markexported": "",
|
||||||
"markreexported": "",
|
"markreexported": "",
|
||||||
"payment": "",
|
"payment": "",
|
||||||
|
"paymentupdate": "",
|
||||||
"stripe": ""
|
"stripe": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2417,6 +2434,7 @@
|
|||||||
"invoice_total_payable": "",
|
"invoice_total_payable": "",
|
||||||
"iou_form": "",
|
"iou_form": "",
|
||||||
"job_costing_ro": "",
|
"job_costing_ro": "",
|
||||||
|
"job_lifecycle_ro": "",
|
||||||
"job_notes": "",
|
"job_notes": "",
|
||||||
"key_tag": "",
|
"key_tag": "",
|
||||||
"labels": {
|
"labels": {
|
||||||
@@ -2583,17 +2601,17 @@
|
|||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"advanced_filters": "",
|
"advanced_filters": "",
|
||||||
"advanced_filters_show": "",
|
|
||||||
"advanced_filters_hide": "",
|
|
||||||
"advanced_filters_filters": "",
|
|
||||||
"advanced_filters_sorters": "",
|
|
||||||
"advanced_filters_filter_field": "",
|
|
||||||
"advanced_filters_sorter_field": "",
|
|
||||||
"advanced_filters_true": "",
|
|
||||||
"advanced_filters_false": "",
|
"advanced_filters_false": "",
|
||||||
"advanced_filters_sorter_direction": "",
|
"advanced_filters_filter_field": "",
|
||||||
"advanced_filters_filter_operator": "",
|
"advanced_filters_filter_operator": "",
|
||||||
"advanced_filters_filter_value": "",
|
"advanced_filters_filter_value": "",
|
||||||
|
"advanced_filters_filters": "",
|
||||||
|
"advanced_filters_hide": "",
|
||||||
|
"advanced_filters_show": "",
|
||||||
|
"advanced_filters_sorter_direction": "",
|
||||||
|
"advanced_filters_sorter_field": "",
|
||||||
|
"advanced_filters_sorters": "",
|
||||||
|
"advanced_filters_true": "",
|
||||||
"dates": "",
|
"dates": "",
|
||||||
"employee": "",
|
"employee": "",
|
||||||
"filterson": "",
|
"filterson": "",
|
||||||
@@ -2675,6 +2693,8 @@
|
|||||||
"job_costing_ro_date_summary": "",
|
"job_costing_ro_date_summary": "",
|
||||||
"job_costing_ro_estimator": "",
|
"job_costing_ro_estimator": "",
|
||||||
"job_costing_ro_ins_co": "",
|
"job_costing_ro_ins_co": "",
|
||||||
|
"job_lifecycle_date_detail": "",
|
||||||
|
"job_lifecycle_date_summary": "",
|
||||||
"jobs_completed_not_invoiced": "",
|
"jobs_completed_not_invoiced": "",
|
||||||
"jobs_invoiced_not_exported": "",
|
"jobs_invoiced_not_exported": "",
|
||||||
"jobs_reconcile": "",
|
"jobs_reconcile": "",
|
||||||
|
|||||||
@@ -217,6 +217,7 @@
|
|||||||
"markexported": "",
|
"markexported": "",
|
||||||
"markforreexport": "",
|
"markforreexport": "",
|
||||||
"new": "",
|
"new": "",
|
||||||
|
"nobilllines": "",
|
||||||
"noneselected": "",
|
"noneselected": "",
|
||||||
"onlycmforinvoiced": "",
|
"onlycmforinvoiced": "",
|
||||||
"printlabels": "",
|
"printlabels": "",
|
||||||
@@ -259,6 +260,7 @@
|
|||||||
"saving": ""
|
"saving": ""
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"ReceivableCustomField": "",
|
||||||
"address1": "",
|
"address1": "",
|
||||||
"address2": "",
|
"address2": "",
|
||||||
"appt_alt_transport": "",
|
"appt_alt_transport": "",
|
||||||
@@ -477,7 +479,6 @@
|
|||||||
"editaccess": ""
|
"editaccess": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ReceivableCustomField": "",
|
|
||||||
"responsibilitycenter": "",
|
"responsibilitycenter": "",
|
||||||
"responsibilitycenter_accountdesc": "",
|
"responsibilitycenter_accountdesc": "",
|
||||||
"responsibilitycenter_accountitem": "",
|
"responsibilitycenter_accountitem": "",
|
||||||
@@ -844,8 +845,8 @@
|
|||||||
"notconfigured": "",
|
"notconfigured": "",
|
||||||
"notfoundsubtitle": "",
|
"notfoundsubtitle": "",
|
||||||
"notfoundtitle": "",
|
"notfoundtitle": "",
|
||||||
"surveycompletetitle": "",
|
"surveycompletesubtitle": "",
|
||||||
"surveycompletesubtitle": ""
|
"surveycompletetitle": ""
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"completedon": "",
|
"completedon": "",
|
||||||
@@ -854,13 +855,13 @@
|
|||||||
"validuntil": ""
|
"validuntil": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"copyright": "",
|
||||||
|
"greeting": "",
|
||||||
|
"intro": "",
|
||||||
"nologgedinuser": "",
|
"nologgedinuser": "",
|
||||||
"nologgedinuser_sub": "",
|
"nologgedinuser_sub": "",
|
||||||
"noneselected": "",
|
"noneselected": "",
|
||||||
"title": "",
|
"title": ""
|
||||||
"greeting": "",
|
|
||||||
"intro": "",
|
|
||||||
"copyright": ""
|
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"created": "",
|
"created": "",
|
||||||
@@ -884,6 +885,7 @@
|
|||||||
"refhrs": ""
|
"refhrs": ""
|
||||||
},
|
},
|
||||||
"titles": {
|
"titles": {
|
||||||
|
"joblifecycle": "",
|
||||||
"labhours": "",
|
"labhours": "",
|
||||||
"larhours": "",
|
"larhours": "",
|
||||||
"monthlyemployeeefficiency": "",
|
"monthlyemployeeefficiency": "",
|
||||||
@@ -1113,6 +1115,7 @@
|
|||||||
"loadingshop": "Chargement des données de la boutique ...",
|
"loadingshop": "Chargement des données de la boutique ...",
|
||||||
"loggingin": "Vous connecter ...",
|
"loggingin": "Vous connecter ...",
|
||||||
"markedexported": "",
|
"markedexported": "",
|
||||||
|
"media": "",
|
||||||
"message": "",
|
"message": "",
|
||||||
"monday": "",
|
"monday": "",
|
||||||
"na": "N / A",
|
"na": "N / A",
|
||||||
@@ -1233,14 +1236,21 @@
|
|||||||
"columns": {
|
"columns": {
|
||||||
"duration": "",
|
"duration": "",
|
||||||
"end": "",
|
"end": "",
|
||||||
|
"human_readable": "",
|
||||||
|
"percentage": "",
|
||||||
"relative_end": "",
|
"relative_end": "",
|
||||||
"relative_start": "",
|
"relative_start": "",
|
||||||
"start": "",
|
"start": "",
|
||||||
|
"status": "",
|
||||||
|
"status_count": "",
|
||||||
"value": ""
|
"value": ""
|
||||||
},
|
},
|
||||||
"content": {
|
"content": {
|
||||||
|
"calculated_based_on": "",
|
||||||
"current_status_accumulated_time": "",
|
"current_status_accumulated_time": "",
|
||||||
"data_unavailable": "",
|
"data_unavailable": "",
|
||||||
|
"joblifecycle": "",
|
||||||
|
"jobs_in_since": "",
|
||||||
"legend_title": "",
|
"legend_title": "",
|
||||||
"loading": "",
|
"loading": "",
|
||||||
"not_available": "",
|
"not_available": "",
|
||||||
@@ -1252,6 +1262,10 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"fetch": "Erreur lors de l'obtention des données du cycle de vie des tâches"
|
"fetch": "Erreur lors de l'obtention des données du cycle de vie des tâches"
|
||||||
|
},
|
||||||
|
"titles": {
|
||||||
|
"dashboard": "",
|
||||||
|
"top_durations": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"job_payments": {
|
"job_payments": {
|
||||||
@@ -1271,6 +1285,7 @@
|
|||||||
"amount": "",
|
"amount": "",
|
||||||
"dateOfPayment": "",
|
"dateOfPayment": "",
|
||||||
"descriptions": "",
|
"descriptions": "",
|
||||||
|
"hint": "",
|
||||||
"payer": "",
|
"payer": "",
|
||||||
"payername": "",
|
"payername": "",
|
||||||
"paymentid": "",
|
"paymentid": "",
|
||||||
@@ -1823,6 +1838,7 @@
|
|||||||
"job": "",
|
"job": "",
|
||||||
"jobcosting": "",
|
"jobcosting": "",
|
||||||
"jobtotals": "",
|
"jobtotals": "",
|
||||||
|
"labor_hrs": "",
|
||||||
"labor_rates_subtotal": "",
|
"labor_rates_subtotal": "",
|
||||||
"laborallocations": "",
|
"laborallocations": "",
|
||||||
"labortotals": "",
|
"labortotals": "",
|
||||||
@@ -2319,6 +2335,7 @@
|
|||||||
"markexported": "",
|
"markexported": "",
|
||||||
"markreexported": "",
|
"markreexported": "",
|
||||||
"payment": "",
|
"payment": "",
|
||||||
|
"paymentupdate": "",
|
||||||
"stripe": ""
|
"stripe": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2417,6 +2434,7 @@
|
|||||||
"invoice_total_payable": "",
|
"invoice_total_payable": "",
|
||||||
"iou_form": "",
|
"iou_form": "",
|
||||||
"job_costing_ro": "",
|
"job_costing_ro": "",
|
||||||
|
"job_lifecycle_ro": "",
|
||||||
"job_notes": "",
|
"job_notes": "",
|
||||||
"key_tag": "",
|
"key_tag": "",
|
||||||
"labels": {
|
"labels": {
|
||||||
@@ -2583,17 +2601,17 @@
|
|||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"advanced_filters": "",
|
"advanced_filters": "",
|
||||||
"advanced_filters_show": "",
|
|
||||||
"advanced_filters_hide": "",
|
|
||||||
"advanced_filters_filters": "",
|
|
||||||
"advanced_filters_sorters": "",
|
|
||||||
"advanced_filters_filter_field": "",
|
|
||||||
"advanced_filters_sorter_field": "",
|
|
||||||
"advanced_filters_true": "",
|
|
||||||
"advanced_filters_false": "",
|
"advanced_filters_false": "",
|
||||||
"advanced_filters_sorter_direction": "",
|
"advanced_filters_filter_field": "",
|
||||||
"advanced_filters_filter_operator": "",
|
"advanced_filters_filter_operator": "",
|
||||||
"advanced_filters_filter_value": "",
|
"advanced_filters_filter_value": "",
|
||||||
|
"advanced_filters_filters": "",
|
||||||
|
"advanced_filters_hide": "",
|
||||||
|
"advanced_filters_show": "",
|
||||||
|
"advanced_filters_sorter_direction": "",
|
||||||
|
"advanced_filters_sorter_field": "",
|
||||||
|
"advanced_filters_sorters": "",
|
||||||
|
"advanced_filters_true": "",
|
||||||
"dates": "",
|
"dates": "",
|
||||||
"employee": "",
|
"employee": "",
|
||||||
"filterson": "",
|
"filterson": "",
|
||||||
@@ -2675,6 +2693,8 @@
|
|||||||
"job_costing_ro_date_summary": "",
|
"job_costing_ro_date_summary": "",
|
||||||
"job_costing_ro_estimator": "",
|
"job_costing_ro_estimator": "",
|
||||||
"job_costing_ro_ins_co": "",
|
"job_costing_ro_ins_co": "",
|
||||||
|
"job_lifecycle_date_detail": "",
|
||||||
|
"job_lifecycle_date_summary": "",
|
||||||
"jobs_completed_not_invoiced": "",
|
"jobs_completed_not_invoiced": "",
|
||||||
"jobs_invoiced_not_exported": "",
|
"jobs_invoiced_not_exported": "",
|
||||||
"jobs_reconcile": "",
|
"jobs_reconcile": "",
|
||||||
|
|||||||
@@ -514,6 +514,14 @@ export const TemplateList = (type, context) => {
|
|||||||
group: "financial",
|
group: "financial",
|
||||||
dms: true,
|
dms: true,
|
||||||
},
|
},
|
||||||
|
job_lifecycle_ro: {
|
||||||
|
title: i18n.t("printcenter.jobs.job_lifecycle_ro"),
|
||||||
|
description: "",
|
||||||
|
subject: i18n.t("printcenter.jobs.job_lifecycle_ro"),
|
||||||
|
key: "job_lifecycle_ro",
|
||||||
|
disabled: false,
|
||||||
|
group: "post",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...(!type || type === "job_special"
|
...(!type || type === "job_special"
|
||||||
@@ -2048,6 +2056,30 @@ export const TemplateList = (type, context) => {
|
|||||||
datedisable: true,
|
datedisable: true,
|
||||||
group: "customers",
|
group: "customers",
|
||||||
},
|
},
|
||||||
|
job_lifecycle_date_detail: {
|
||||||
|
title: i18n.t("reportcenter.templates.job_lifecycle_date_detail"),
|
||||||
|
subject: i18n.t("reportcenter.templates.job_lifecycle_date_detail"),
|
||||||
|
key: "job_lifecycle_date_detail",
|
||||||
|
//idtype: "vendor",
|
||||||
|
disabled: false,
|
||||||
|
rangeFilter: {
|
||||||
|
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||||
|
field: i18n.t("jobs.fields.date_invoiced"),
|
||||||
|
},
|
||||||
|
group: "jobs",
|
||||||
|
},
|
||||||
|
job_lifecycle_date_summary: {
|
||||||
|
title: i18n.t("reportcenter.templates.job_lifecycle_date_summary"),
|
||||||
|
subject: i18n.t("reportcenter.templates.job_lifecycle_date_summary"),
|
||||||
|
key: "job_lifecycle_date_summary",
|
||||||
|
//idtype: "vendor",
|
||||||
|
disabled: false,
|
||||||
|
rangeFilter: {
|
||||||
|
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||||
|
field: i18n.t("jobs.fields.date_invoiced"),
|
||||||
|
},
|
||||||
|
group: "jobs",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...(!type || type === "courtesycarcontract"
|
...(!type || type === "courtesycarcontract"
|
||||||
|
|||||||
@@ -569,6 +569,13 @@
|
|||||||
table:
|
table:
|
||||||
name: parts_orders
|
name: parts_orders
|
||||||
schema: public
|
schema: public
|
||||||
|
- name: tasks
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on:
|
||||||
|
column: billid
|
||||||
|
table:
|
||||||
|
name: tasks
|
||||||
|
schema: public
|
||||||
insert_permissions:
|
insert_permissions:
|
||||||
- role: user
|
- role: user
|
||||||
permission:
|
permission:
|
||||||
@@ -818,6 +825,13 @@
|
|||||||
table:
|
table:
|
||||||
name: inventory
|
name: inventory
|
||||||
schema: public
|
schema: public
|
||||||
|
- name: ioevents
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on:
|
||||||
|
column: bodyshopid
|
||||||
|
table:
|
||||||
|
name: ioevents
|
||||||
|
schema: public
|
||||||
- name: jobs
|
- name: jobs
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
@@ -846,6 +860,13 @@
|
|||||||
table:
|
table:
|
||||||
name: phonebook
|
name: phonebook
|
||||||
schema: public
|
schema: public
|
||||||
|
- name: tasks
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on:
|
||||||
|
column: bodyshopid
|
||||||
|
table:
|
||||||
|
name: tasks
|
||||||
|
schema: public
|
||||||
- name: timetickets
|
- name: timetickets
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
@@ -948,6 +969,7 @@
|
|||||||
- md_rbac
|
- md_rbac
|
||||||
- md_referral_sources
|
- md_referral_sources
|
||||||
- md_responsibility_centers
|
- md_responsibility_centers
|
||||||
|
- md_ro_guard
|
||||||
- md_ro_statuses
|
- md_ro_statuses
|
||||||
- md_tasks_presets
|
- md_tasks_presets
|
||||||
- md_to_emails
|
- md_to_emails
|
||||||
@@ -1047,6 +1069,7 @@
|
|||||||
- md_rbac
|
- md_rbac
|
||||||
- md_referral_sources
|
- md_referral_sources
|
||||||
- md_responsibility_centers
|
- md_responsibility_centers
|
||||||
|
- md_ro_guard
|
||||||
- md_ro_statuses
|
- md_ro_statuses
|
||||||
- md_tasks_presets
|
- md_tasks_presets
|
||||||
- md_to_emails
|
- md_to_emails
|
||||||
@@ -2675,6 +2698,13 @@
|
|||||||
- table:
|
- table:
|
||||||
name: ioevents
|
name: ioevents
|
||||||
schema: public
|
schema: public
|
||||||
|
object_relationships:
|
||||||
|
- name: bodyshop
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on: bodyshopid
|
||||||
|
- name: user
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on: useremail
|
||||||
- table:
|
- table:
|
||||||
name: job_ar_schema
|
name: job_ar_schema
|
||||||
schema: public
|
schema: public
|
||||||
@@ -2824,6 +2854,13 @@
|
|||||||
table:
|
table:
|
||||||
name: parts_order_lines
|
name: parts_order_lines
|
||||||
schema: public
|
schema: public
|
||||||
|
- name: tasks
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on:
|
||||||
|
column: joblineid
|
||||||
|
table:
|
||||||
|
name: tasks
|
||||||
|
schema: public
|
||||||
insert_permissions:
|
insert_permissions:
|
||||||
- role: user
|
- role: user
|
||||||
permission:
|
permission:
|
||||||
@@ -3311,6 +3348,13 @@
|
|||||||
table:
|
table:
|
||||||
name: scoreboard
|
name: scoreboard
|
||||||
schema: public
|
schema: public
|
||||||
|
- name: tasks
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on:
|
||||||
|
column: jobid
|
||||||
|
table:
|
||||||
|
name: tasks
|
||||||
|
schema: public
|
||||||
- name: timetickets
|
- name: timetickets
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
@@ -4200,7 +4244,7 @@
|
|||||||
interval_sec: 10
|
interval_sec: 10
|
||||||
num_retries: 0
|
num_retries: 0
|
||||||
timeout_sec: 60
|
timeout_sec: 60
|
||||||
webhook: https://worktest.home.irony.online
|
webhook_from_env: HASURA_API_URL
|
||||||
headers:
|
headers:
|
||||||
- name: event-secret
|
- name: event-secret
|
||||||
value_from_env: EVENT_SECRET
|
value_from_env: EVENT_SECRET
|
||||||
@@ -5008,6 +5052,13 @@
|
|||||||
table:
|
table:
|
||||||
name: parts_order_lines
|
name: parts_order_lines
|
||||||
schema: public
|
schema: public
|
||||||
|
- name: tasks
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on:
|
||||||
|
column: partsorderid
|
||||||
|
table:
|
||||||
|
name: tasks
|
||||||
|
schema: public
|
||||||
insert_permissions:
|
insert_permissions:
|
||||||
- role: user
|
- role: user
|
||||||
permission:
|
permission:
|
||||||
@@ -5623,6 +5674,129 @@
|
|||||||
_eq: X-Hasura-User-Id
|
_eq: X-Hasura-User-Id
|
||||||
- active:
|
- active:
|
||||||
_eq: true
|
_eq: true
|
||||||
|
- table:
|
||||||
|
name: tasks
|
||||||
|
schema: public
|
||||||
|
object_relationships:
|
||||||
|
- name: bill
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on: billid
|
||||||
|
- name: bodyshop
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on: bodyshopid
|
||||||
|
- name: job
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on: jobid
|
||||||
|
- name: jobline
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on: joblineid
|
||||||
|
- name: parts_order
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on: partsorderid
|
||||||
|
- name: user
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on: assigned_to
|
||||||
|
- name: userByCreatedBy
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on: created_by
|
||||||
|
insert_permissions:
|
||||||
|
- role: user
|
||||||
|
permission:
|
||||||
|
check:
|
||||||
|
bodyshop:
|
||||||
|
associations:
|
||||||
|
_and:
|
||||||
|
- user:
|
||||||
|
authid:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
- active:
|
||||||
|
_eq: true
|
||||||
|
columns:
|
||||||
|
- completed
|
||||||
|
- deleted
|
||||||
|
- priority
|
||||||
|
- assigned_to
|
||||||
|
- created_by
|
||||||
|
- description
|
||||||
|
- title
|
||||||
|
- completed_at
|
||||||
|
- created_at
|
||||||
|
- deleted_at
|
||||||
|
- due_date
|
||||||
|
- remind_at
|
||||||
|
- updated_at
|
||||||
|
- billid
|
||||||
|
- bodyshopid
|
||||||
|
- id
|
||||||
|
- jobid
|
||||||
|
- joblineid
|
||||||
|
- partsorderid
|
||||||
|
select_permissions:
|
||||||
|
- role: user
|
||||||
|
permission:
|
||||||
|
columns:
|
||||||
|
- completed
|
||||||
|
- deleted
|
||||||
|
- priority
|
||||||
|
- assigned_to
|
||||||
|
- created_by
|
||||||
|
- description
|
||||||
|
- title
|
||||||
|
- completed_at
|
||||||
|
- created_at
|
||||||
|
- deleted_at
|
||||||
|
- due_date
|
||||||
|
- remind_at
|
||||||
|
- updated_at
|
||||||
|
- billid
|
||||||
|
- bodyshopid
|
||||||
|
- id
|
||||||
|
- jobid
|
||||||
|
- joblineid
|
||||||
|
- partsorderid
|
||||||
|
filter:
|
||||||
|
bodyshop:
|
||||||
|
associations:
|
||||||
|
_and:
|
||||||
|
- user:
|
||||||
|
authid:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
- active:
|
||||||
|
_eq: true
|
||||||
|
allow_aggregations: true
|
||||||
|
update_permissions:
|
||||||
|
- role: user
|
||||||
|
permission:
|
||||||
|
columns:
|
||||||
|
- completed
|
||||||
|
- deleted
|
||||||
|
- priority
|
||||||
|
- assigned_to
|
||||||
|
- created_by
|
||||||
|
- description
|
||||||
|
- title
|
||||||
|
- completed_at
|
||||||
|
- created_at
|
||||||
|
- deleted_at
|
||||||
|
- due_date
|
||||||
|
- remind_at
|
||||||
|
- updated_at
|
||||||
|
- billid
|
||||||
|
- bodyshopid
|
||||||
|
- id
|
||||||
|
- jobid
|
||||||
|
- joblineid
|
||||||
|
- partsorderid
|
||||||
|
filter:
|
||||||
|
bodyshop:
|
||||||
|
associations:
|
||||||
|
_and:
|
||||||
|
- user:
|
||||||
|
authid:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
- active:
|
||||||
|
_eq: true
|
||||||
|
check: null
|
||||||
- table:
|
- table:
|
||||||
name: timetickets
|
name: timetickets
|
||||||
schema: public
|
schema: public
|
||||||
@@ -6006,6 +6180,13 @@
|
|||||||
table:
|
table:
|
||||||
name: exportlog
|
name: exportlog
|
||||||
schema: public
|
schema: public
|
||||||
|
- name: ioevents
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on:
|
||||||
|
column: useremail
|
||||||
|
table:
|
||||||
|
name: ioevents
|
||||||
|
schema: public
|
||||||
- name: messages
|
- name: messages
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
@@ -6034,6 +6215,20 @@
|
|||||||
table:
|
table:
|
||||||
name: parts_orders
|
name: parts_orders
|
||||||
schema: public
|
schema: public
|
||||||
|
- name: tasks
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on:
|
||||||
|
column: assigned_to
|
||||||
|
table:
|
||||||
|
name: tasks
|
||||||
|
schema: public
|
||||||
|
- name: tasksByCreatedBy
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on:
|
||||||
|
column: created_by
|
||||||
|
table:
|
||||||
|
name: tasks
|
||||||
|
schema: public
|
||||||
- name: timetickets
|
- name: timetickets
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
DROP TABLE "public"."tasks";
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
CREATE TABLE "public"."tasks" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "title" text NOT NULL, "description" Text, "deleted" boolean NOT NULL DEFAULT false, "deleted_at" timestamptz, "due_date" timestamptz, "created_by" text NOT NULL, "assigned_to" Text, "completed" boolean NOT NULL DEFAULT false, "completed_at" timestamptz, "remind_at" timestamptz, "priority" numeric, "bodyshopid" UUID NOT NULL, "jobid" UUID NOT NULL, "joblineid" UUID, "partsorderid" UUID, "billid" UUID, PRIMARY KEY ("id") , FOREIGN KEY ("created_by") REFERENCES "public"."users"("email") ON UPDATE restrict ON DELETE restrict, FOREIGN KEY ("assigned_to") REFERENCES "public"."users"("email") ON UPDATE restrict ON DELETE restrict, FOREIGN KEY ("bodyshopid") REFERENCES "public"."bodyshops"("id") ON UPDATE restrict ON DELETE restrict, FOREIGN KEY ("jobid") REFERENCES "public"."jobs"("id") ON UPDATE cascade ON DELETE cascade, FOREIGN KEY ("joblineid") REFERENCES "public"."joblines"("id") ON UPDATE set null ON DELETE set null, FOREIGN KEY ("partsorderid") REFERENCES "public"."parts_orders"("id") ON UPDATE set null ON DELETE set null, FOREIGN KEY ("billid") REFERENCES "public"."bills"("id") ON UPDATE set null ON DELETE set null);
|
||||||
|
CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
DECLARE
|
||||||
|
_new record;
|
||||||
|
BEGIN
|
||||||
|
_new := NEW;
|
||||||
|
_new."updated_at" = NOW();
|
||||||
|
RETURN _new;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
CREATE TRIGGER "set_public_tasks_updated_at"
|
||||||
|
BEFORE UPDATE ON "public"."tasks"
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"();
|
||||||
|
COMMENT ON TRIGGER "set_public_tasks_updated_at" ON "public"."tasks"
|
||||||
|
IS 'trigger to set value of column "updated_at" to current timestamp on row update';
|
||||||
|
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||||
@@ -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 "md_ro_guard" jsonb
|
||||||
|
-- null default jsonb_build_object();
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
alter table "public"."bodyshops" add column "md_ro_guard" jsonb
|
||||||
|
null default jsonb_build_object();
|
||||||
@@ -201,19 +201,24 @@ exports.default = async (req, res) => {
|
|||||||
} finally {
|
} finally {
|
||||||
sftp.end();
|
sftp.end();
|
||||||
}
|
}
|
||||||
sendServerEmail({
|
// sendServerEmail({
|
||||||
subject: `Kaizen Report ${moment().format("MM-DD-YY")}`,
|
// subject: `Kaizen Report ${moment().format("MM-DD-YY")}`,
|
||||||
text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
|
// text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
|
||||||
Uploaded: ${JSON.stringify(
|
// Uploaded: ${JSON.stringify(
|
||||||
allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
|
// allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
|
||||||
null,
|
// null,
|
||||||
2
|
// 2
|
||||||
)}
|
// )}
|
||||||
`,
|
// `,
|
||||||
});
|
// });
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(200).json(error);
|
res.status(200).json(error);
|
||||||
|
sendServerEmail({
|
||||||
|
subject: `Kaizen Report ${moment().format("MM-DD-YY @ HH:mm:ss")}`,
|
||||||
|
text: `Errors: JSON.stringify(error)}
|
||||||
|
All Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2175,4 +2175,12 @@ exports.COMPLETE_SURVEY = `mutation COMPLETE_SURVEY($surveyId: uuid!, $survey: c
|
|||||||
update_csi(where: { id: { _eq: $surveyId } }, _set: $survey) {
|
update_csi(where: { id: { _eq: $surveyId } }, _set: $survey) {
|
||||||
affected_rows
|
affected_rows
|
||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
exports.GET_JOBS_BY_PKS = `query GET_JOBS_BY_PKS($ids: [uuid!]!) {
|
||||||
|
jobs(where: {id: {_in: $ids}}) {
|
||||||
|
id
|
||||||
|
shopid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -8,21 +8,15 @@ const moment = require("moment");
|
|||||||
const logger = require("../utils/logger");
|
const logger = require("../utils/logger");
|
||||||
|
|
||||||
require("dotenv").config({
|
require("dotenv").config({
|
||||||
path: path.resolve(
|
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
|
||||||
process.cwd(),
|
|
||||||
`.env.${process.env.NODE_ENV || "development"}`
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const domain = process.env.NODE_ENV ? "secure" : "test";
|
const domain = process.env.NODE_ENV ? "secure" : "test";
|
||||||
|
|
||||||
const {
|
const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager");
|
||||||
SecretsManagerClient,
|
|
||||||
GetSecretValueCommand,
|
|
||||||
} = require("@aws-sdk/client-secrets-manager");
|
|
||||||
|
|
||||||
const client = new SecretsManagerClient({
|
const client = new SecretsManagerClient({
|
||||||
region: "ca-central-1",
|
region: "ca-central-1" //TODO-AIO: instance manager required when merged to master-AIO
|
||||||
});
|
});
|
||||||
|
|
||||||
const gqlClient = require("../graphql-client/graphql-client").client;
|
const gqlClient = require("../graphql-client/graphql-client").client;
|
||||||
@@ -32,7 +26,7 @@ const getShopCredentials = async (bodyshop) => {
|
|||||||
if (process.env.NODE_ENV === undefined) {
|
if (process.env.NODE_ENV === undefined) {
|
||||||
return {
|
return {
|
||||||
merchantkey: process.env.INTELLIPAY_MERCHANTKEY,
|
merchantkey: process.env.INTELLIPAY_MERCHANTKEY,
|
||||||
apikey: process.env.INTELLIPAY_APIKEY,
|
apikey: process.env.INTELLIPAY_APIKEY
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,26 +36,20 @@ const getShopCredentials = async (bodyshop) => {
|
|||||||
const secret = await client.send(
|
const secret = await client.send(
|
||||||
new GetSecretValueCommand({
|
new GetSecretValueCommand({
|
||||||
SecretId: `intellipay-credentials-${bodyshop.imexshopid}`,
|
SecretId: `intellipay-credentials-${bodyshop.imexshopid}`,
|
||||||
VersionStage: "AWSCURRENT", // VersionStage defaults to AWSCURRENT if unspecified
|
VersionStage: "AWSCURRENT" // VersionStage defaults to AWSCURRENT if unspecified
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
return JSON.parse(secret.SecretString);
|
return JSON.parse(secret.SecretString);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
error: error.message,
|
error: error.message
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.lightbox_credentials = async (req, res) => {
|
exports.lightbox_credentials = async (req, res) => {
|
||||||
logger.log(
|
logger.log("intellipay-lightbox-credentials", "DEBUG", req.user?.email, null, null);
|
||||||
"intellipay-lightbox-credentials",
|
|
||||||
"DEBUG",
|
|
||||||
req.user?.email,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
const shopCredentials = await getShopCredentials(req.body.bodyshop);
|
const shopCredentials = await getShopCredentials(req.body.bodyshop);
|
||||||
|
|
||||||
@@ -75,11 +63,9 @@ exports.lightbox_credentials = async (req, res) => {
|
|||||||
headers: { "content-type": "application/x-www-form-urlencoded" },
|
headers: { "content-type": "application/x-www-form-urlencoded" },
|
||||||
data: qs.stringify({
|
data: qs.stringify({
|
||||||
...shopCredentials,
|
...shopCredentials,
|
||||||
operatingenv: "businessattended",
|
operatingenv: "businessattended"
|
||||||
}),
|
}),
|
||||||
url: `https://${domain}.cpteller.com/api/custapi.cfc?method=autoterminal${
|
url: `https://${domain}.cpteller.com/api/custapi.cfc?method=autoterminal${req.body.refresh ? "_refresh" : ""}` //autoterminal_refresh
|
||||||
req.body.refresh ? "_refresh" : ""
|
|
||||||
}`, //autoterminal_refresh
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await axios(options);
|
const response = await axios(options);
|
||||||
@@ -87,13 +73,9 @@ exports.lightbox_credentials = async (req, res) => {
|
|||||||
res.send(response.data);
|
res.send(response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
logger.log(
|
logger.log("intellipay-lightbox-credentials-error", "ERROR", req.user?.email, null, {
|
||||||
"intellipay-lightbox-credentials-error",
|
error: JSON.stringify(error)
|
||||||
"ERROR",
|
});
|
||||||
req.user?.email,
|
|
||||||
null,
|
|
||||||
{ error: JSON.stringify(error) }
|
|
||||||
);
|
|
||||||
res.json({ error });
|
res.json({ error });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -112,9 +94,9 @@ exports.payment_refund = async (req, res) => {
|
|||||||
method: "payment_refund",
|
method: "payment_refund",
|
||||||
...shopCredentials,
|
...shopCredentials,
|
||||||
paymentid: req.body.paymentid,
|
paymentid: req.body.paymentid,
|
||||||
amount: req.body.amount,
|
amount: req.body.amount
|
||||||
}),
|
}),
|
||||||
url: `https://${domain}.cpteller.com/api/26/webapi.cfc?method=payment_refund`,
|
url: `https://${domain}.cpteller.com/api/26/webapi.cfc?method=payment_refund`
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await axios(options);
|
const response = await axios(options);
|
||||||
@@ -123,7 +105,7 @@ exports.payment_refund = async (req, res) => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
logger.log("intellipay-refund-error", "ERROR", req.user?.email, null, {
|
logger.log("intellipay-refund-error", "ERROR", req.user?.email, null, {
|
||||||
error: JSON.stringify(error),
|
error: JSON.stringify(error)
|
||||||
});
|
});
|
||||||
res.json({ error });
|
res.json({ error });
|
||||||
}
|
}
|
||||||
@@ -141,15 +123,13 @@ exports.generate_payment_url = async (req, res) => {
|
|||||||
data: qs.stringify({
|
data: qs.stringify({
|
||||||
...shopCredentials,
|
...shopCredentials,
|
||||||
//...req.body,
|
//...req.body,
|
||||||
amount: Dinero({ amount: Math.round(req.body.amount * 100) }).toFormat(
|
amount: Dinero({ amount: Math.round(req.body.amount * 100) }).toFormat("0.00"),
|
||||||
"0.00"
|
|
||||||
),
|
|
||||||
account: req.body.account,
|
account: req.body.account,
|
||||||
invoice: req.body.invoice,
|
invoice: req.body.invoice,
|
||||||
createshorturl: true,
|
createshorturl: true
|
||||||
//The postback URL is set at the CP teller global terminal settings page.
|
//The postback URL is set at the CP teller global terminal settings page.
|
||||||
}),
|
}),
|
||||||
url: `https://${domain}.cpteller.com/api/custapi.cfc?method=generate_lightbox_url`,
|
url: `https://${domain}.cpteller.com/api/custapi.cfc?method=generate_lightbox_url`
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await axios(options);
|
const response = await axios(options);
|
||||||
@@ -158,56 +138,100 @@ exports.generate_payment_url = async (req, res) => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
logger.log("intellipay-payment-url-error", "ERROR", req.user?.email, null, {
|
logger.log("intellipay-payment-url-error", "ERROR", req.user?.email, null, {
|
||||||
error: JSON.stringify(error),
|
error: JSON.stringify(error)
|
||||||
});
|
});
|
||||||
res.json({ error });
|
res.json({ error });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.postback = async (req, res) => {
|
exports.postback = async (req, res) => {
|
||||||
logger.log("intellipay-postback", "ERROR", req.user?.email, null, req.body);
|
logger.log("intellipay-postback", "DEBUG", req.user?.email, null, req.body);
|
||||||
const { body: values } = req;
|
const { body: values } = req;
|
||||||
|
|
||||||
if (!values.invoice) {
|
const comment = Buffer.from(values?.comment, "base64").toString();
|
||||||
|
|
||||||
|
if ((!values.invoice || values.invoice === "") && !comment) {
|
||||||
|
//invoice is specified through the pay link. Comment by IO.
|
||||||
|
logger.log("intellipay-postback-ignored", "DEBUG", req.user?.email, null, req.body);
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO query job by account name
|
|
||||||
const job = await gqlClient.request(queries.GET_JOB_BY_PK, {
|
|
||||||
id: values.invoice,
|
|
||||||
});
|
|
||||||
// TODO add mutation to database
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
|
if (values.invoice) {
|
||||||
paymentInput: {
|
//This is a link email that's been sent out.
|
||||||
amount: values.total,
|
const job = await gqlClient.request(queries.GET_JOB_BY_PK, {
|
||||||
transactionid: `C00 ${values.authcode}`,
|
id: values.invoice
|
||||||
payer: "Customer",
|
});
|
||||||
type: values.cardtype,
|
|
||||||
jobid: values.invoice,
|
|
||||||
date: moment(Date.now()),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, {
|
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
|
||||||
paymentResponse: {
|
paymentInput: {
|
||||||
amount: values.total,
|
amount: values.total,
|
||||||
bodyshopid: job.jobs_by_pk.shopid,
|
transactionid: values.authcode,
|
||||||
paymentid: paymentResult.id,
|
payer: "Customer",
|
||||||
jobid: values.invoice,
|
type: values.cardtype,
|
||||||
declinereason: "Approved",
|
jobid: values.invoice,
|
||||||
ext_paymentid: values.paymentid,
|
date: moment(Date.now())
|
||||||
successful: true,
|
}
|
||||||
response: values,
|
});
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
res.send({ message: "Postback Successful" });
|
const responseResults = await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, {
|
||||||
|
paymentResponse: {
|
||||||
|
amount: values.total,
|
||||||
|
bodyshopid: job.jobs_by_pk.shopid,
|
||||||
|
paymentid: paymentResult.id,
|
||||||
|
jobid: values.invoice,
|
||||||
|
declinereason: "Approved",
|
||||||
|
ext_paymentid: values.paymentid,
|
||||||
|
successful: true,
|
||||||
|
response: values
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.log("intellipay-postback-link-success", "DEBUG", req.user?.email, null, {
|
||||||
|
iprequest: values,
|
||||||
|
responseResults,
|
||||||
|
paymentResult
|
||||||
|
});
|
||||||
|
res.sendStatus(200);
|
||||||
|
} else if (comment) {
|
||||||
|
//This has been triggered by IO and may have multiple jobs.
|
||||||
|
const partialPayments = JSON.parse(comment);
|
||||||
|
const jobs = await gqlClient.request(queries.GET_JOBS_BY_PKS, {
|
||||||
|
ids: partialPayments.map((p) => p.jobid)
|
||||||
|
});
|
||||||
|
|
||||||
|
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
|
||||||
|
paymentInput: partialPayments.map((p) => ({
|
||||||
|
amount: p.amount,
|
||||||
|
transactionid: values.authcode,
|
||||||
|
payer: "Customer",
|
||||||
|
type: values.cardtype,
|
||||||
|
jobid: p.jobid,
|
||||||
|
date: moment(Date.now()),
|
||||||
|
payment_responses: {
|
||||||
|
data: {
|
||||||
|
amount: values.total,
|
||||||
|
bodyshopid: jobs.jobs[0].shopid,
|
||||||
|
jobid: p.jobid,
|
||||||
|
declinereason: "Approved",
|
||||||
|
ext_paymentid: values.paymentid,
|
||||||
|
successful: true,
|
||||||
|
response: values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
logger.log("intellipay-postback-app-success", "DEBUG", req.user?.email, null, {
|
||||||
|
iprequest: values,
|
||||||
|
paymentResult
|
||||||
|
});
|
||||||
|
res.sendStatus(200);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("intellipay-postback-error", "ERROR", req.user?.email, null, {
|
logger.log("intellipay-postback-error", "ERROR", req.user?.email, null, {
|
||||||
error: JSON.stringify(error),
|
error: JSON.stringify(error),
|
||||||
body: req.body,
|
body: req.body
|
||||||
});
|
});
|
||||||
res.status(400).json({ succesful: false, error: error.message });
|
res.status(400).json({ succesful: false, error: error.message });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const queries = require("../graphql-client/queries");
|
|||||||
const moment = require("moment");
|
const moment = require("moment");
|
||||||
const durationToHumanReadable = require("../utils/durationToHumanReadable");
|
const durationToHumanReadable = require("../utils/durationToHumanReadable");
|
||||||
const calculateStatusDuration = require("../utils/calculateStatusDuration");
|
const calculateStatusDuration = require("../utils/calculateStatusDuration");
|
||||||
|
const getLifecycleStatusColor = require("../utils/getLifecycleStatusColor");
|
||||||
|
|
||||||
const jobLifecycle = async (req, res) => {
|
const jobLifecycle = async (req, res) => {
|
||||||
// Grab the jobids and statuses from the request body
|
// Grab the jobids and statuses from the request body
|
||||||
@@ -28,12 +29,12 @@ const jobLifecycle = async (req, res) => {
|
|||||||
jobIDs,
|
jobIDs,
|
||||||
transitions: []
|
transitions: []
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const transitionsByJobId = _.groupBy(resp.transitions, 'jobid');
|
const transitionsByJobId = _.groupBy(resp.transitions, 'jobid');
|
||||||
|
|
||||||
const groupedTransitions = {};
|
const groupedTransitions = {};
|
||||||
|
const allDurations = [];
|
||||||
|
|
||||||
for (let jobId in transitionsByJobId) {
|
for (let jobId in transitionsByJobId) {
|
||||||
let lifecycle = transitionsByJobId[jobId].map(transition => {
|
let lifecycle = transitionsByJobId[jobId].map(transition => {
|
||||||
@@ -53,15 +54,57 @@ const jobLifecycle = async (req, res) => {
|
|||||||
return transition;
|
return transition;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const durations = calculateStatusDuration(lifecycle, statuses);
|
||||||
|
|
||||||
groupedTransitions[jobId] = {
|
groupedTransitions[jobId] = {
|
||||||
lifecycle: lifecycle,
|
lifecycle,
|
||||||
durations: calculateStatusDuration(lifecycle, statuses),
|
durations
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (durations?.summations) {
|
||||||
|
allDurations.push(durations.summations);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const finalSummations = [];
|
||||||
|
const flatGroupedAllDurations = _.groupBy(allDurations.flat(),'status');
|
||||||
|
|
||||||
|
const finalStatusCounts = Object.keys(flatGroupedAllDurations).reduce((acc, status) => {
|
||||||
|
acc[status] = flatGroupedAllDurations[status].length;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
// Calculate total value of all statuses
|
||||||
|
const finalTotal = Object.values(flatGroupedAllDurations).reduce((total, statusArr) => {
|
||||||
|
return total + statusArr.reduce((acc, curr) => acc + curr.value, 0);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
Object.keys(flatGroupedAllDurations).forEach(status => {
|
||||||
|
const value = flatGroupedAllDurations[status].reduce((acc, curr) => acc + curr.value, 0);
|
||||||
|
const humanReadable = durationToHumanReadable(moment.duration(value));
|
||||||
|
const percentage = (value / finalTotal) * 100;
|
||||||
|
const color = getLifecycleStatusColor(status);
|
||||||
|
const roundedPercentage = `${Math.round(percentage)}%`;
|
||||||
|
finalSummations.push({
|
||||||
|
status,
|
||||||
|
value,
|
||||||
|
humanReadable,
|
||||||
|
percentage,
|
||||||
|
color,
|
||||||
|
roundedPercentage
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
jobIDs,
|
jobIDs,
|
||||||
transition: groupedTransitions,
|
transition: groupedTransitions,
|
||||||
|
durations: {
|
||||||
|
jobs: jobIDs.length,
|
||||||
|
summations: finalSummations,
|
||||||
|
totalStatuses: finalSummations.length,
|
||||||
|
total: finalTotal,
|
||||||
|
statusCounts: finalStatusCounts,
|
||||||
|
humanReadable: durationToHumanReadable(moment.duration(finalTotal))
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,7 @@
|
|||||||
const durationToHumanReadable = require("./durationToHumanReadable");
|
const durationToHumanReadable = require("./durationToHumanReadable");
|
||||||
const moment = require("moment");
|
const moment = require("moment");
|
||||||
|
const getLifecycleStatusColor = require("./getLifecycleStatusColor");
|
||||||
const _ = require("lodash");
|
const _ = require("lodash");
|
||||||
const crypto = require('crypto');
|
|
||||||
|
|
||||||
const getColor = (key) => {
|
|
||||||
const hash = crypto.createHash('sha256');
|
|
||||||
hash.update(key);
|
|
||||||
const hashedKey = hash.digest('hex');
|
|
||||||
const num = parseInt(hashedKey, 16);
|
|
||||||
return '#' + (num % 16777215).toString(16).padStart(6, '0');
|
|
||||||
};
|
|
||||||
|
|
||||||
const calculateStatusDuration = (transitions, statuses) => {
|
const calculateStatusDuration = (transitions, statuses) => {
|
||||||
let statusDuration = {};
|
let statusDuration = {};
|
||||||
@@ -33,26 +25,16 @@ const calculateStatusDuration = (transitions, statuses) => {
|
|||||||
if (!transition.prev_value) {
|
if (!transition.prev_value) {
|
||||||
statusDuration[transition.value] = {
|
statusDuration[transition.value] = {
|
||||||
value: duration,
|
value: duration,
|
||||||
humanReadable: transition.duration_readable
|
humanReadable: durationToHumanReadable(moment.duration(duration))
|
||||||
};
|
};
|
||||||
} else if (!transition.next_value) {
|
} else {
|
||||||
if (statusDuration[transition.value]) {
|
if (statusDuration[transition.value]) {
|
||||||
statusDuration[transition.value].value += duration;
|
statusDuration[transition.value].value += duration;
|
||||||
statusDuration[transition.value].humanReadable = transition.duration_readable;
|
statusDuration[transition.value].humanReadable = durationToHumanReadable(moment.duration(statusDuration[transition.value].value));
|
||||||
} else {
|
} else {
|
||||||
statusDuration[transition.value] = {
|
statusDuration[transition.value] = {
|
||||||
value: duration,
|
value: duration,
|
||||||
humanReadable: transition.duration_readable
|
humanReadable: durationToHumanReadable(moment.duration(duration))
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (statusDuration[transition.value]) {
|
|
||||||
statusDuration[transition.value].value += duration;
|
|
||||||
statusDuration[transition.value].humanReadable = transition.duration_readable;
|
|
||||||
} else {
|
|
||||||
statusDuration[transition.value] = {
|
|
||||||
value: duration,
|
|
||||||
humanReadable: transition.duration_readable
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,7 +61,7 @@ const calculateStatusDuration = (transitions, statuses) => {
|
|||||||
value,
|
value,
|
||||||
humanReadable,
|
humanReadable,
|
||||||
percentage: statusDuration[status].percentage,
|
percentage: statusDuration[status].percentage,
|
||||||
color: getColor(status),
|
color: getLifecycleStatusColor(status),
|
||||||
roundedPercentage: `${Math.round(statusDuration[status].percentage)}%`
|
roundedPercentage: `${Math.round(statusDuration[status].percentage)}%`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
11
server/utils/getLifecycleStatusColor.js
Normal file
11
server/utils/getLifecycleStatusColor.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
const getLifecycleStatusColor = (key) => {
|
||||||
|
const hash = crypto.createHash('sha256');
|
||||||
|
hash.update(key);
|
||||||
|
const hashedKey = hash.digest('hex');
|
||||||
|
const num = parseInt(hashedKey, 16);
|
||||||
|
return '#' + (num % 16777215).toString(16).padStart(6, '0');
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = getLifecycleStatusColor;
|
||||||
Reference in New Issue
Block a user