Merged in feature/america (pull request #982)

Feature/america
This commit is contained in:
Patrick Fic
2023-09-15 19:58:23 +00:00
72 changed files with 3723 additions and 11881 deletions

View File

@@ -132,6 +132,57 @@ jobs:
to: "s3://rome-online-production/"
- jira/notify
test-rome-hasura-migrate:
docker:
- image: cimg/node:16.15.0
parameters:
secret:
type: string
default: $HASURA_ROME_TEST_SECRET
working_directory: ~/repo/hasura
steps:
- checkout:
path: ~/repo
- run:
name: Execute migration
command: |
npm install hasura-cli -g
echo ${HASURA_TEST_SECRET}
hasura migrate apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
hasura metadata apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
hasura metadata reload --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
test-rome-app-build:
docker:
- image: cimg/node:16.15.0
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- restore_cache:
name: Restore Yarn Package Cache
keys:
- yarn-packages-{{ checksum "yarn.lock" }}
- run:
name: Install Dependencies
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
- save_cache:
name: Save Yarn Package Cache
key: yarn-packages-{{ checksum "yarn.lock" }}
paths:
- ~/.cache/yarn
- run: yarn run build:test
- aws-s3/sync:
from: build
to: "s3://rome-online-test/"
- jira/notify
test-hasura-migrate:
docker:
- image: cimg/node:16.15.0
@@ -250,6 +301,15 @@ workflows:
filters:
branches:
only: test
- test-rome-app-build:
filters:
branches:
only: rome/test
- test-rome-hasura-migrate:
secret: ${HASURA_ROME_TEST_SECRET}
filters:
branches:
only: rome/test
#- admin-app-build:
#filters:
#branches:

View File

@@ -1,4 +1,4 @@
<babeledit_project version="1.2" be_version="2.7.1">
<babeledit_project be_version="2.7.1" version="1.2">
<!--
BabelEdit project file
@@ -1009,27 +1009,6 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>smspaymentreminder</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>suggesteddates</name>
<definition_loaded>false</definition_loaded>
@@ -16312,6 +16291,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>copied</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>copylink</name>
<definition_loaded>false</definition_loaded>
@@ -16648,6 +16648,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>sendbysms</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>senderrortosupport</name>
<definition_loaded>false</definition_loaded>
@@ -19360,6 +19381,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>openingip</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>title</name>
<definition_loaded>false</definition_loaded>
@@ -33131,27 +33173,6 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>paymentremindersms</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>phonebook</name>
<definition_loaded>false</definition_loaded>
@@ -37601,6 +37622,32 @@
<folder_node>
<name>payments</name>
<children>
<folder_node>
<name>actions</name>
<children>
<concept_node>
<name>generatepaymentlink</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node>
<name>errors</name>
<children>
@@ -37646,6 +37693,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>inserting</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node>
@@ -38118,6 +38186,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>smspaymentreminder</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>title</name>
<definition_loaded>false</definition_loaded>

View File

@@ -1,14 +1,14 @@
REACT_APP_GRAPHQL_ENDPOINT=https://db.test.bodyshop.app/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.bodyshop.app/v1/graphql
REACT_APP_GA_CODE=231099835
REACT_APP_FIREBASE_CONFIG={ "apiKey":"AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c", "authDomain":"imex-test.firebaseapp.com", "projectId":"imex-test", "storageBucket":"imex-test.appspot.com", "messagingSenderId":"991923618608", "appId":"1:991923618608:web:633437569cdad78299bef5", "measurementId":"G-TW0XLZEH18"}
REACT_APP_GRAPHQL_ENDPOINT=https://db.test.romeonline.io/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.romeonline.io/v1/graphql
REACT_APP_GA_CODE=231103507
REACT_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
REACT_APP_CLOUDINARY_API_KEY=473322739956866
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BN2GcDPjipR5MTEosO5dT4CfQ3cmrdBIsI4juoOQrRijn_5aRiHlwj1mlq0W145mOusx6xynEKl_tvYJhpCc9lo'
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=https://api.romeonline.io/
REACT_APP_REPORTS_SERVER_URL=https://reports.romeonline.io
REACT_APP_AXIOS_BASE_API_URL=https://api.test.romeonline.io/
REACT_APP_REPORTS_SERVER_URL=https://reports.test.romeonline.io
REACT_APP_IS_TEST=true
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc

View File

@@ -8,18 +8,6 @@
<meta name="description" content="Rome Online" />
<!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
<link rel="apple-touch-icon" href="logo192.png" />
<script type="text/javascript">
window.$crisp = [];
window.CRISP_WEBSITE_ID = "36724f62-2eb0-4b29-9cdd-9905fb99913e";
(function () {
d = document;
s = d.createElement("script");
s.src = "https://client.crisp.chat/l.js";
s.async = 1;
d.getElementsByTagName("head")[0].appendChild(s);
})();
</script>
<script>
!(function () {
"use strict";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -97,6 +97,11 @@ function Header({
{},
bodyshop && bodyshop.imexshopid
);
const { ImEXPay } = useTreatments(
["ImEXPay"],
{},
bodyshop && bodyshop.imexshopid
);
const { t } = useTranslation();
@@ -243,19 +248,20 @@ function Header({
>
{t("menus.header.enterpayment")}
</Menu.Item>
{/* TODO: Enter Card Payment */}
<Menu.Item
key="entercardpayments"
onClick={() => {
setCardPaymentContext({
actions: {},
context: null,
});
}}
icon={<Icon component={FaCreditCard} />}
>
{t("menus.header.entercardpayment")}
</Menu.Item>
{ImEXPay.treatment === "on" && (
<Menu.Item
key="entercardpayments"
onClick={() => {
setCardPaymentContext({
actions: {},
context: {},
});
}}
icon={<Icon component={FaCreditCard} />}
>
{t("menus.header.entercardpayment")}
</Menu.Item>
)}
<Menu.Divider key="div5" />
<Menu.Item key="timetickets" icon={<FieldTimeOutlined />}>
<Link to="/manage/timetickets">
@@ -383,20 +389,12 @@ function Header({
<Menu.Item
key="help"
onClick={() => {
window.open("https://help.imex.online/", "_blank");
window.open("https://rometech.com/", "_blank");
}}
icon={<Icon component={QuestionCircleFilled} />}
>
{t("menus.header.help")}
</Menu.Item>
<Menu.Item
key="rescue"
onClick={() => {
window.open("https://imexrescue.com/", "_blank");
}}
>
{t("menus.header.rescueme")}
</Menu.Item>
<Menu.Item key="shiftclock">
<Link to="/manage/shiftclock">{t("menus.header.shiftclock")}</Link>
</Menu.Item>

View File

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

View File

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

View File

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

View File

@@ -77,7 +77,7 @@ export function JoblineBulkAssign({
hours.toFixed(1)
),
});
setSelectedLines([]);
setVisible(false);
}
} catch (error) {

View File

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

View File

@@ -6,8 +6,9 @@ import {
useQuery,
} from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react";
import { Col, notification, Row } from "antd";
import { Col, Row, notification } from "antd";
import Axios from "axios";
import _ from "lodash";
import moment from "moment";
import queryString from "query-string";
import React, { useCallback, useEffect, useState } from "react";
@@ -29,8 +30,8 @@ import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import confirmDialog from "../../utils/asyncConfirm";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import confirmDialog from "../../utils/asyncConfirm";
import CriticalPartsScan from "../../utils/criticalPartsScan";
import AlertComponent from "../alert/alert.component";
import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component";
@@ -40,7 +41,6 @@ import OwnerFindModalContainer from "../owner-find-modal/owner-find-modal.contai
import { GetSupplementDelta } from "./jobs-available-supplement.estlines.util";
import HeaderFields from "./jobs-available-supplement.headerfields";
import JobsAvailableTableComponent from "./jobs-available-table.component";
import _ from "lodash";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -139,6 +139,7 @@ export function JobsAvailableContainer({
// owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"),
// job_totals: newTotals,
date_open: moment(),
status: bodyshop.md_ro_statuses.default_imported,
notes: {
data: {
created_by: currentUser.email,
@@ -160,6 +161,10 @@ export function JobsAvailableContainer({
delete newJob.vehicle;
}
if (typeof newJob.kmin === "string") {
newJob.kmin = null;
}
insertNewJob({
variables: {
job: newJob,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
import { useTreatments } from "@splitsoftware/splitio-react";
import { Card, Col, Input, Row, Space, Typography } from "antd";
import _ from "lodash";
import React, { useState } from "react";
@@ -23,8 +24,13 @@ export function PrintCenterJobsComponent({ printCenterModal, bodyshop }) {
const { id: jobId, job } = printCenterModal.context;
const tempList = TemplateList("job", {});
const { t } = useTranslation();
const { Enhanced_Payroll } = useTreatments(
["Enhanced_Payroll"],
{},
bodyshop.imexshopid
);
const JobsReportsList =
const Templates =
bodyshop.cdk_dealerid === null && bodyshop.pbs_serialnumber === null
? Object.keys(tempList)
.map((key) => {
@@ -51,7 +57,26 @@ export function PrintCenterJobsComponent({ printCenterModal, bodyshop }) {
bodyshop.region_config.includes(Object.keys(temp.regions)) ===
true)
);
const JobsReportsList =
Enhanced_Payroll.treatment === "on"
? Object.keys(Templates)
.map((key) => {
return Templates[key];
})
.filter(
(temp) =>
temp.enhanced_payroll === undefined ||
temp.enhanced_payroll === true
)
: Object.keys(Templates)
.map((key) => {
return Templates[key];
})
.filter(
(temp) =>
temp.enhanced_payroll === undefined ||
temp.enhanced_payroll === false
);
const filteredJobsReportsList =
search !== ""
? JobsReportsList.filter((r) =>

View File

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

View File

@@ -1,4 +1,5 @@
import { useLazyQuery } from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react";
import {
Button,
Card,
@@ -19,6 +20,7 @@ import { createStructuredSelector } from "reselect";
import { QUERY_ACTIVE_EMPLOYEES } from "../../graphql/employees.queries";
import { QUERY_ALL_VENDORS } from "../../graphql/vendors.queries";
import { selectReportCenter } from "../../redux/modals/modals.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import DatePIckerRanges from "../../utils/DatePickerRanges";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
@@ -27,6 +29,7 @@ import VendorSearchSelect from "../vendor-search-select/vendor-search-select.com
import "./report-center-modal.styles.scss";
const mapStateToProps = createStructuredSelector({
reportCenterModal: selectReportCenter,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
@@ -36,16 +39,38 @@ export default connect(
mapDispatchToProps
)(ReportCenterModalComponent);
export function ReportCenterModalComponent({ reportCenterModal }) {
export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) {
const [form] = Form.useForm();
const [search, setSearch] = useState("");
const { Enhanced_Payroll } = useTreatments(
["Enhanced_Payroll"],
{},
bodyshop.imexshopid
);
const [loading, setLoading] = useState(false);
const { t } = useTranslation();
const Templates = TemplateList("report_center");
const ReportsList = Object.keys(Templates).map((key) => {
return Templates[key];
});
const ReportsList =
Enhanced_Payroll.treatment === "on"
? Object.keys(Templates)
.map((key) => {
return Templates[key];
})
.filter(
(temp) =>
temp.enhanced_payroll === undefined ||
temp.enhanced_payroll === true
)
: Object.keys(Templates)
.map((key) => {
return Templates[key];
})
.filter(
(temp) =>
temp.enhanced_payroll === undefined ||
temp.enhanced_payroll === false
);
const { visible } = reportCenterModal;
const [callVendorQuery, { data: vendorData, called: vendorCalled }] =

View File

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

View File

@@ -293,12 +293,6 @@ export function ShopInfoGeneral({ form, bodyshop }) {
<Form.Item
label={t("bodyshop.fields.federal_tax_id")}
name="federal_tax_id"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>

View File

@@ -2,6 +2,7 @@ import { useMutation } from "@apollo/client";
import { Button, Card, Form, notification, Space } from "antd";
import axios from "axios";
import moment from "moment";
import momenttz from "moment-timezone";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -51,14 +52,20 @@ export function TechClockInContainer({
const handleFinish = async (values) => {
setLoading(true);
const theTime = (await axios.post("/utils/time")).data;
const result = await insertTimeTicket({
variables: {
timeTicketInput: [
{
bodyshopid: bodyshop.id,
employeeid: technician.id,
date: moment(theTime).format("YYYY-MM-DD"),
date:
typeof bodyshop.timezone === "string"
? momenttz.tz(theTime, bodyshop.timezone).format("YYYY-MM-DD")
: typeof bodyshop.timezone === "number"
? moment(theTime)
.format("YYYY-MM-DD")
.utcOffset(bodyshop.timezone)
: moment(theTime).format("YYYY-MM-DD"),
clockon: moment(theTime),
jobid: values.jobid,
cost_center: values.cost_center,
@@ -130,7 +137,7 @@ export function TechClockInContainer({
>
{t("timetickets.actions.enter")}
</Button>
<TechJobPrintTickets />
<TechJobPrintTickets attendacePrint={false} />
<Button
type="primary"
onClick={() => form.submit()}

View File

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

View File

@@ -153,7 +153,7 @@ export function TimeTicketModalContainer({
if (!!changedFields.cost_center && !!EmployeeAutoCompleteData) {
form.setFieldsValue({
ciecacode:
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || Enhanced_Payroll.treatments === 'on'
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || Enhanced_Payroll.treatment === 'on'
? changedFields.cost_center
: Object.keys(
bodyshop.md_responsibility_centers.defaults.costs

View File

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

View File

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

View File

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

View File

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

View File

@@ -574,7 +574,6 @@ export const GET_JOB_BY_PK = gql`
est_co_nm
est_ct_fn
est_ct_ln
est_ph1
est_ea
selling_dealer
@@ -756,8 +755,8 @@ export const GET_JOB_BY_PK = gql`
jobid
amount
payer
paymentnum
created_at
stripeid
transactionid
memo
date

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -197,6 +197,14 @@ export function* signInSuccessSaga({ payload }) {
LogRocket.identify(payload.email);
try {
// window.$crisp.push([
// "set",
// "user:nickname",
// [payload.displayName || payload.email],
// ]);
// window.$crisp.push(["set", "session:segments", [["rome-user"]]]);
Sentry.setUser({
email: payload.email,
username: payload.displayName || payload.email,
@@ -280,6 +288,10 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
)
);
try {
// window.$crisp.push(["set", "user:company", [payload.shopname]]);
// if (authRecord[0] && authRecord[0].user.validemail) {
// window.$crisp.push(["set", "user:email", [authRecord[0].user.email]]);
// }
} catch (error) {
console.error("Couldnt find $crisp.");
}

View File

@@ -495,7 +495,7 @@
"lam": "Mechanical",
"lar": "Refinish",
"las": "Structural",
"lau": "Detail",
"lau": "User Defined",
"local_tax": "Local Tax",
"mapa": "Paint Materials",
"mash": "Shop Materials",
@@ -614,6 +614,7 @@
"laborrates": "Labor Rates",
"licensing": "Licensing",
"md_tasks_presets": "Tasks Presets",
"md_parts_scan": "Parts Scan Rules",
"md_to_emails": "Preset To Emails",
"md_to_emails_emails": "Emails",
"messagingpresets": "Messaging Presets",
@@ -1019,6 +1020,7 @@
"cancel": "Cancel",
"clear": "Clear",
"close": "Close",
"copied": "Copied!",
"copylink": "Copy Link",
"create": "Create",
"delete": "Delete",
@@ -1035,6 +1037,7 @@
"saveandnew": "Save and New",
"selectall": "Select All",
"send": "Send",
"sendbysms": "Send by SMS",
"senderrortosupport": "Send Error to Support",
"submit": "Submit",
"tryagain": "Try Again",
@@ -1096,6 +1099,7 @@
"passwordsdonotmatch": "The passwords you have entered do not match.",
"print": "Print",
"refresh": "Refresh",
"reports": "Reports",
"required": "Required",
"saturday": "Saturday",
"search": "Search...",
@@ -1195,24 +1199,26 @@
},
"job_payments": {
"buttons": {
"goback": "Cancel",
"goback": "Go Back",
"proceedtopayment": "Proceed to Payment",
"refundpayment": "Refund Payment"
},
"notifications": {
"error": {
"description": "Please try again. Make sure the refund amount does not exceeds the payment amount.",
"title": "Error Refunding Payment"
"openingip": "Error connecting to IntelliPay service.",
"title": "Error placing refund"
}
},
"titles": {
"amount": "Amount",
"dateOfPayment": "Date",
"descriptions": "Description",
"dateOfPayment": "Date of Payment",
"descriptions": "Payment Details",
"payer": "Payer",
"payername": "Payer Name",
"paymentid": "Payment ID",
"paymenttype": "Type",
"paymentid": "Payment Reference ID",
"paymentnum": "Payment Number",
"paymenttype": "Payment Type",
"refundamount": "Refund Amount",
"transactionid": "Transaction ID"
}
@@ -1933,7 +1939,7 @@
"customers": "Customers",
"dashboard": "Dashboard",
"enterbills": "Enter Bills",
"entercardpayment": "Enter Card Payments",
"entercardpayment": "New Card Charge",
"enterpayment": "Enter Payments",
"entertimeticket": "Enter Time Tickets",
"export": "Export",
@@ -1945,7 +1951,6 @@
"newjob": "Create New Job",
"owners": "Owners",
"parts-queue": "Parts Queue",
"paymentremindersms": "Send Payment Reminder via SMS",
"phonebook": "Phonebook",
"productionboard": "Production Board - Visual",
"productionlist": "Production Board - List",
@@ -2231,9 +2236,13 @@
}
},
"payments": {
"actions": {
"generatepaymentlink": "Generate Payment Link"
},
"errors": {
"exporting": "Error exporting payment(s). {{error}}",
"exporting-partner": "Error exporting to partner. Please check the partner interaction log for more errors."
"exporting-partner": "Error exporting to partner. Please check the partner interaction log for more errors.",
"inserting": "Error inserting payment. {{error}}"
},
"fields": {
"amount": "Amount",
@@ -2260,6 +2269,7 @@
"markforreexport": "Mark for Re-export",
"new": "New Payment",
"signup": "Please contact support to sign up for electronic payments.",
"smspaymentreminder": "This is {{shopname}} reminding you about your balance of {{amount}}. To pay, click the following link {{payment_link}}.",
"title": "Payments",
"totalpayments": "Total Payments"
},
@@ -2343,6 +2353,7 @@
"appointment_confirmation": "Appointment Confirmation",
"appointment_reminder": "Appointment Reminder",
"casl_authorization": "CASL Authorization",
"committed_timetickets_ro": "Committed Time Tickets",
"coversheet_landscape": "Coversheet (Landscape)",
"coversheet_portrait": "Coversheet Portrait",
"csi_invitation": "CSI Invitation",
@@ -2380,6 +2391,7 @@
"mpi_final_acct_sheet": "MPI - Final Accounting Sheet (Direct Repair)",
"mpi_final_repair_acct_sheet": "MPI - Final Accounting Sheet",
"paint_grid": "Paint Grid",
"parts_dispatch": "Parts Dispatch",
"parts_invoice_label_single": "Parts Label Single",
"parts_label_multiple": "Parts Label - Multi",
"parts_label_single": "Parts Label - Single",
@@ -2414,7 +2426,8 @@
"worksheet_sorted_by_operation": "Worksheet by Operation",
"worksheet_sorted_by_operation_no_hours": "Worksheet by Operation (No Hours)",
"worksheet_sorted_by_operation_part_type": "Worksheet by Operation & Part Type",
"worksheet_sorted_by_operation_type": "Worksheet by Operation Type"
"worksheet_sorted_by_operation_type": "Worksheet by Operation Type",
"worksheet_sorted_by_team": "Worksheet by Team"
},
"labels": {
"groups": {
@@ -2441,6 +2454,7 @@
"subjects": {
"jobs": {
"individual_job_note": "Job Note RO: {{ro_number}}",
"parts_dispatch": "Parts Dispatch RO: {{ro_number}}",
"parts_order": "Parts Order PO: {{ro_number}} - {{name}}",
"parts_return_slip": "Parts Return PO: {{ro_number}} - {{name}}",
"sublet_order": "Sublet Order PO: {{ro_number}} - {{name}}"
@@ -2561,6 +2575,9 @@
"attendance_detail": "Attendance (All Employees)",
"attendance_employee": "Employee Attendance",
"attendance_summary": "Attendance Summary (All Employees)",
"committed_timetickets": "Committed Time Tickets",
"committed_timetickets_employee": "Committed Employee Time Tickets",
"committed_timetickets_summary": "Committed Time Tickets Summary",
"credits_not_received_date": "Credits not Received by Date",
"credits_not_received_date_vendorid": "Credits not Received by Vendor",
"csi": "CSI Responses",
@@ -2659,6 +2676,7 @@
"timetickets_summary": "Time Tickets Summary",
"unclaimed_hrs": "Unflagged Hours",
"void_ros": "Void ROs",
"work_in_progress_committed_labour": "Work in Progress - Committed Labor",
"work_in_progress_jobs": "Work in Progress - Jobs",
"work_in_progress_labour": "Work in Progress - Labor",
"work_in_progress_payables": "Work in Progress - Payables"
@@ -2765,6 +2783,7 @@
"clockoff": "Clock Off",
"clockon": "Clocked In",
"committed": "Committed",
"committed_at": "Committed At",
"cost_center": "Cost Center",
"created_by": "Created By",
"date": "Ticket Date",

View File

@@ -63,7 +63,6 @@
"scheduledfor": "Cita programada para:",
"severalerrorsfound": "",
"smartscheduling": "",
"smspaymentreminder": "",
"suggesteddates": ""
},
"successes": {
@@ -613,6 +612,7 @@
"jobstatuses": "",
"laborrates": "",
"licensing": "",
"md_parts_scan": "",
"md_tasks_presets": "",
"md_to_emails": "",
"md_to_emails_emails": "",
@@ -1019,6 +1019,7 @@
"cancel": "",
"clear": "",
"close": "",
"copied": "",
"copylink": "",
"create": "",
"delete": "Borrar",
@@ -1035,6 +1036,7 @@
"saveandnew": "",
"selectall": "",
"send": "",
"sendbysms": "",
"senderrortosupport": "",
"submit": "",
"tryagain": "",
@@ -1096,6 +1098,7 @@
"passwordsdonotmatch": "",
"print": "",
"refresh": "",
"reports": "",
"required": "",
"saturday": "",
"search": "Buscar...",
@@ -1202,6 +1205,7 @@
"notifications": {
"error": {
"description": "",
"openingip": "",
"title": ""
}
},
@@ -1212,6 +1216,7 @@
"payer": "",
"payername": "",
"paymentid": "",
"paymentnum": "",
"paymenttype": "",
"refundamount": "",
"transactionid": ""
@@ -1945,7 +1950,6 @@
"newjob": "",
"owners": "propietarios",
"parts-queue": "",
"paymentremindersms": "",
"phonebook": "",
"productionboard": "",
"productionlist": "",
@@ -2231,9 +2235,13 @@
}
},
"payments": {
"actions": {
"generatepaymentlink": ""
},
"errors": {
"exporting": "",
"exporting-partner": ""
"exporting-partner": "",
"inserting": ""
},
"fields": {
"amount": "",
@@ -2260,6 +2268,7 @@
"markforreexport": "",
"new": "",
"signup": "",
"smspaymentreminder": "",
"title": "",
"totalpayments": ""
},
@@ -2343,6 +2352,7 @@
"appointment_confirmation": "",
"appointment_reminder": "",
"casl_authorization": "",
"committed_timetickets_ro": "",
"coversheet_landscape": "",
"coversheet_portrait": "",
"csi_invitation": "",
@@ -2380,6 +2390,7 @@
"mpi_final_acct_sheet": "",
"mpi_final_repair_acct_sheet": "",
"paint_grid": "",
"parts_dispatch": "",
"parts_invoice_label_single": "",
"parts_label_multiple": "",
"parts_label_single": "",
@@ -2414,7 +2425,8 @@
"worksheet_sorted_by_operation": "",
"worksheet_sorted_by_operation_no_hours": "",
"worksheet_sorted_by_operation_part_type": "",
"worksheet_sorted_by_operation_type": ""
"worksheet_sorted_by_operation_type": "",
"worksheet_sorted_by_team": ""
},
"labels": {
"groups": {
@@ -2441,6 +2453,7 @@
"subjects": {
"jobs": {
"individual_job_note": "",
"parts_dispatch": "",
"parts_order": "",
"parts_return_slip": "",
"sublet_order": ""
@@ -2561,6 +2574,9 @@
"attendance_detail": "",
"attendance_employee": "",
"attendance_summary": "",
"committed_timetickets": "",
"committed_timetickets_employee": "",
"committed_timetickets_summary": "",
"credits_not_received_date": "",
"credits_not_received_date_vendorid": "",
"csi": "",
@@ -2659,6 +2675,7 @@
"timetickets_summary": "",
"unclaimed_hrs": "",
"void_ros": "",
"work_in_progress_committed_labour": "",
"work_in_progress_jobs": "",
"work_in_progress_labour": "",
"work_in_progress_payables": ""
@@ -2765,6 +2782,7 @@
"clockoff": "",
"clockon": "",
"committed": "",
"committed_at": "",
"cost_center": "",
"created_by": "",
"date": "",

View File

@@ -63,7 +63,6 @@
"scheduledfor": "Rendez-vous prévu pour:",
"severalerrorsfound": "",
"smartscheduling": "",
"smspaymentreminder": "",
"suggesteddates": ""
},
"successes": {
@@ -613,6 +612,7 @@
"jobstatuses": "",
"laborrates": "",
"licensing": "",
"md_parts_scan": "",
"md_tasks_presets": "",
"md_to_emails": "",
"md_to_emails_emails": "",
@@ -1019,6 +1019,7 @@
"cancel": "",
"clear": "",
"close": "",
"copied": "",
"copylink": "",
"create": "",
"delete": "Effacer",
@@ -1035,6 +1036,7 @@
"saveandnew": "",
"selectall": "",
"send": "",
"sendbysms": "",
"senderrortosupport": "",
"submit": "",
"tryagain": "",
@@ -1096,6 +1098,7 @@
"passwordsdonotmatch": "",
"print": "",
"refresh": "",
"reports": "",
"required": "",
"saturday": "",
"search": "Chercher...",
@@ -1202,6 +1205,7 @@
"notifications": {
"error": {
"description": "",
"openingip": "",
"title": ""
}
},
@@ -1212,6 +1216,7 @@
"payer": "",
"payername": "",
"paymentid": "",
"paymentnum": "",
"paymenttype": "",
"refundamount": "",
"transactionid": ""
@@ -1945,7 +1950,6 @@
"newjob": "",
"owners": "Propriétaires",
"parts-queue": "",
"paymentremindersms": "",
"phonebook": "",
"productionboard": "",
"productionlist": "",
@@ -2231,9 +2235,13 @@
}
},
"payments": {
"actions": {
"generatepaymentlink": ""
},
"errors": {
"exporting": "",
"exporting-partner": ""
"exporting-partner": "",
"inserting": ""
},
"fields": {
"amount": "",
@@ -2260,6 +2268,7 @@
"markforreexport": "",
"new": "",
"signup": "",
"smspaymentreminder": "",
"title": "",
"totalpayments": ""
},
@@ -2343,6 +2352,7 @@
"appointment_confirmation": "",
"appointment_reminder": "",
"casl_authorization": "",
"committed_timetickets_ro": "",
"coversheet_landscape": "",
"coversheet_portrait": "",
"csi_invitation": "",
@@ -2380,6 +2390,7 @@
"mpi_final_acct_sheet": "",
"mpi_final_repair_acct_sheet": "",
"paint_grid": "",
"parts_dispatch": "",
"parts_invoice_label_single": "",
"parts_label_multiple": "",
"parts_label_single": "",
@@ -2414,7 +2425,8 @@
"worksheet_sorted_by_operation": "",
"worksheet_sorted_by_operation_no_hours": "",
"worksheet_sorted_by_operation_part_type": "",
"worksheet_sorted_by_operation_type": ""
"worksheet_sorted_by_operation_type": "",
"worksheet_sorted_by_team": ""
},
"labels": {
"groups": {
@@ -2441,6 +2453,7 @@
"subjects": {
"jobs": {
"individual_job_note": "",
"parts_dispatch": "",
"parts_order": "",
"parts_return_slip": "",
"sublet_order": ""
@@ -2561,6 +2574,9 @@
"attendance_detail": "",
"attendance_employee": "",
"attendance_summary": "",
"committed_timetickets": "",
"committed_timetickets_employee": "",
"committed_timetickets_summary": "",
"credits_not_received_date": "",
"credits_not_received_date_vendorid": "",
"csi": "",
@@ -2659,6 +2675,7 @@
"timetickets_summary": "",
"unclaimed_hrs": "",
"void_ros": "",
"work_in_progress_committed_labour": "",
"work_in_progress_jobs": "",
"work_in_progress_labour": "",
"work_in_progress_payables": ""
@@ -2765,6 +2782,7 @@
"clockoff": "",
"clockon": "",
"committed": "",
"committed_at": "",
"cost_center": "",
"created_by": "",
"date": "",

View File

@@ -189,6 +189,16 @@ export const TemplateList = (type, context) => {
key: "worksheet_by_line_number",
disabled: false,
group: "worksheet",
enhanced_payroll: false,
},
worksheet_by_line_number_enhanced: {
title: i18n.t("printcenter.jobs.worksheet_by_line_number"),
description: "",
subject: i18n.t("printcenter.jobs.worksheet_by_line_number"),
key: "worksheet_by_line_number_enhanced",
disabled: false,
group: "worksheet",
enhanced_payroll: true,
},
worksheet_sorted_by_operation_type: {
title: i18n.t(
@@ -201,6 +211,20 @@ export const TemplateList = (type, context) => {
key: "worksheet_sorted_by_operation_type",
disabled: false,
group: "worksheet",
enhanced_payroll: false,
},
worksheet_sorted_by_operation_type_enhanced: {
title: i18n.t(
"printcenter.jobs.worksheet_sorted_by_operation_type"
),
description: "",
subject: i18n.t(
"printcenter.jobs.worksheet_sorted_by_operation_type"
),
key: "worksheet_sorted_by_operation_type_enhanced",
disabled: false,
group: "worksheet",
enhanced_payroll: true,
},
worksheet_sorted_by_operation: {
title: i18n.t("printcenter.jobs.worksheet_sorted_by_operation"),
@@ -209,6 +233,16 @@ export const TemplateList = (type, context) => {
key: "worksheet_sorted_by_operation",
disabled: false,
group: "worksheet",
enhanced_payroll: false,
},
worksheet_sorted_by_operation_enhanced: {
title: i18n.t("printcenter.jobs.worksheet_sorted_by_operation"),
description: "",
subject: i18n.t("printcenter.jobs.worksheet_sorted_by_operation"),
key: "worksheet_sorted_by_operation_enhanced",
disabled: false,
group: "worksheet",
enhanced_payroll: true,
},
worksheet_sorted_by_operation_no_hours: {
title: i18n.t(
@@ -221,6 +255,20 @@ export const TemplateList = (type, context) => {
key: "worksheet_sorted_by_operation_no_hours",
disabled: false,
group: "worksheet",
enhanced_payroll: false,
},
worksheet_sorted_by_operation_no_hours_enhanced: {
title: i18n.t(
"printcenter.jobs.worksheet_sorted_by_operation_no_hours"
),
description: "",
subject: i18n.t(
"printcenter.jobs.worksheet_sorted_by_operation_no_hours"
),
key: "worksheet_sorted_by_operation_no_hours_enhanced",
disabled: false,
group: "worksheet",
enhanced_payroll: true,
},
worksheet_sorted_by_operation_part_type: {
title: i18n.t(
@@ -233,6 +281,20 @@ export const TemplateList = (type, context) => {
key: "worksheet_sorted_by_operation_part_type",
disabled: false,
group: "worksheet",
enhanced_payroll: false,
},
worksheet_sorted_by_operation_part_type_enhanced: {
title: i18n.t(
"printcenter.jobs.worksheet_sorted_by_operation_part_type"
),
description: "",
subject: i18n.t(
"printcenter.jobs.worksheet_sorted_by_operation_part_type"
),
key: "worksheet_sorted_by_operation_part_type_enhanced",
disabled: false,
group: "worksheet",
enhanced_payroll: true,
},
payments_by_job: {
title: i18n.t("printcenter.jobs.payments_by_job"),
@@ -514,6 +576,24 @@ export const TemplateList = (type, context) => {
group: "financial",
dms: true,
},
worksheet_sorted_by_team: {
title: i18n.t("printcenter.jobs.worksheet_sorted_by_team"),
description: "",
subject: i18n.t("printcenter.jobs.worksheet_sorted_by_team"),
key: "worksheet_sorted_by_team",
disabled: false,
group: "worksheet",
enhanced_payroll: true,
},
committed_timetickets_ro: {
title: i18n.t("printcenter.jobs.committed_timetickets_ro"),
description: "",
subject: i18n.t("printcenter.jobs.committed_timetickets_ro"),
key: "committed_timetickets_ro",
disabled: false,
group: "financial",
enhanced_payroll: true,
},
}
: {}),
...(!type || type === "job_special"
@@ -1144,6 +1224,10 @@ export const TemplateList = (type, context) => {
key: "timetickets_employee",
idtype: "employee",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.timetickets"),
field: i18n.t("timetickets.fields.date"),
},
group: "payroll",
},
attendance_detail: {
@@ -1582,6 +1666,24 @@ export const TemplateList = (type, context) => {
},
group: "jobs",
},
work_in_progress_committed_labour: {
title: i18n.t(
"reportcenter.templates.work_in_progress_committed_labour"
),
description: "",
subject: i18n.t(
"reportcenter.templates.work_in_progress_committed_labour"
),
key: "work_in_progress_committed_labour",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_open"),
},
group: "jobs",
enhanced_payroll: true,
},
work_in_progress_payables: {
title: i18n.t("reportcenter.templates.work_in_progress_payables"),
description: "",
@@ -1938,6 +2040,52 @@ export const TemplateList = (type, context) => {
},
group: "jobs",
},
committed_timetickets: {
title: i18n.t("reportcenter.templates.committed_timetickets"),
subject: i18n.t("reportcenter.templates.committed_timetickets"),
key: "committed_timetickets",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.timetickets"),
field: i18n.t("timetickets.fields.committed_at"),
},
group: "payroll",
enhanced_payroll: true,
},
committed_timetickets_employee: {
title: i18n.t(
"reportcenter.templates.committed_timetickets_employee"
),
subject: i18n.t(
"reportcenter.templates.committed_timetickets_employee"
),
key: "committed_timetickets_employee",
idtype: "employee",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.timetickets"),
field: i18n.t("timetickets.fields.committed_at"),
},
group: "payroll",
enhanced_payroll: true,
},
committed_timetickets_summary: {
title: i18n.t(
"reportcenter.templates.committed_timetickets_summary"
),
subject: i18n.t(
"reportcenter.templates.committed_timetickets_summary"
),
key: "committed_timetickets_summary",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.timetickets"),
field: i18n.t("timetickets.fields.committed_at"),
},
group: "payroll",
enhanced_payroll: true,
},
}
: {}),
...(!type || type === "courtesycarcontract"

View File

@@ -3273,6 +3273,8 @@
- ca_gst_registrant
- cat_no
- category
- cieca_pfl
- cieca_pft
- cieca_stl
- cieca_ttl
- ciecaid
@@ -3314,6 +3316,7 @@
- date_repairstarted
- date_scheduled
- date_towin
- date_void
- ded_amt
- ded_note
- ded_status
@@ -3495,7 +3498,6 @@
- v_model_yr
- v_vin
- vehicleid
- date_void
- voided
select_permissions:
- role: user
@@ -3539,6 +3541,8 @@
- ca_gst_registrant
- cat_no
- category
- cieca_pfl
- cieca_pft
- cieca_stl
- cieca_ttl
- ciecaid
@@ -3580,6 +3584,7 @@
- date_repairstarted
- date_scheduled
- date_towin
- date_void
- ded_amt
- ded_note
- ded_status
@@ -3762,7 +3767,6 @@
- v_model_yr
- v_vin
- vehicleid
- date_void
- voided
filter:
bodyshop:
@@ -3816,6 +3820,8 @@
- ca_gst_registrant
- cat_no
- category
- cieca_pfl
- cieca_pft
- cieca_stl
- cieca_ttl
- ciecaid
@@ -3857,6 +3863,7 @@
- date_repairstarted
- date_scheduled
- date_towin
- date_void
- ded_amt
- ded_note
- ded_status
@@ -4039,7 +4046,6 @@
- v_model_yr
- v_vin
- vehicleid
- date_void
- voided
filter:
bodyshop:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -20,7 +20,7 @@ require("dotenv").config({
async function RunTheTest() {
const bodyshopids = ["6c63a820-542c-497e-8c82-0cc38fb2bbca"];
const bearerToken = `Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Ijk3OWVkMTU1OTdhYjM1Zjc4MjljZTc0NDMwN2I3OTNiN2ViZWIyZjAiLCJ0eXAiOiJKV1QifQ.eyJodHRwczovL2hhc3VyYS5pby9qd3QvY2xhaW1zIjp7IngtaGFzdXJhLWRlZmF1bHQtcm9sZSI6InVzZXIiLCJ4LWhhc3VyYS1hbGxvd2VkLXJvbGVzIjpbInVzZXIiXSwieC1oYXN1cmEtdXNlci1pZCI6InQ2WW0xTkRsQ0RPUFpyM0Y5Ymd1V0g0TGhTWDIifSwiaXNzIjoiaHR0cHM6Ly9zZWN1cmV0b2tlbi5nb29nbGUuY29tL3JvbWUtcHJvZC0xIiwiYXVkIjoicm9tZS1wcm9kLTEiLCJhdXRoX3RpbWUiOjE2NzkzNDc4NzAsInVzZXJfaWQiOiJ0NlltMU5EbENET1BacjNGOWJndVdINExoU1gyIiwic3ViIjoidDZZbTFORGxDRE9QWnIzRjliZ3VXSDRMaFNYMiIsImlhdCI6MTY3OTk1NDk3MiwiZXhwIjoxNjc5OTU4NTcyLCJlbWFpbCI6InBhdHJpY2tAcm9tZS5kZXYiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsicGF0cmlja0Byb21lLmRldiJdfSwic2lnbl9pbl9wcm92aWRlciI6InBhc3N3b3JkIn19.Dnq_xo5tffFf-LK0qD_iieUa_UYe4cJqOxcuJnRGH0aqirMMeQLRR4B_Z3pOsD3T20ML3qZMQNUKx-Ivz1mfyK_aA7_4GKtHRKOpIrAyssw_l5aXuCAEmC8iLQHDGvKi7Vp8LsTMPKqjJSjtaW2zuFqcIGrqncWkBMYSnCKjCFsKjryp35hQiIynAN1W0ajgjmFZHCy7hG1h4wFtLKNXEAGxWA0tE7m7ZZBZk3W7J3nMbYiMuGZfw0y2yYeILQGw3UW6sb9B2Jx2bAR3x-GWhPzQHNZEPolE-andm900cFgdph1z7eBE5P2udc2rp8JsAPdUdovt8ZImhCUeE5wD6g`;
const bearerToken = `Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImM2MGI5ZGUwODBmZmFmYmZjMTgzMzllY2Q0NGFjNzdmN2ZhNGU4ZDMiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiUm9tZSBEZXZlbG9wbWVudCIsImh0dHBzOi8vaGFzdXJhLmlvL2p3dC9jbGFpbXMiOnsieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoiYWRtaW4iLCJ4LWhhc3VyYS1hbGxvd2VkLXJvbGVzIjpbImFkbWluIl0sIngtaGFzdXJhLXVzZXItaWQiOiJ0NlltMU5EbENET1BacjNGOWJndVdINExoU1gyIn0sImlvYWRtaW4iOnRydWUsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9yb21lLXByb2QtMSIsImF1ZCI6InJvbWUtcHJvZC0xIiwiYXV0aF90aW1lIjoxNjkyODk5ODE2LCJ1c2VyX2lkIjoidDZZbTFORGxDRE9QWnIzRjliZ3VXSDRMaFNYMiIsInN1YiI6InQ2WW0xTkRsQ0RPUFpyM0Y5Ymd1V0g0TGhTWDIiLCJpYXQiOjE2OTMyNTA1NjIsImV4cCI6MTY5MzI1NDE2MiwiZW1haWwiOiJwYXRyaWNrQHJvbWUuZGV2IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWlsIjpbInBhdHJpY2tAcm9tZS5kZXYiXX0sInNpZ25faW5fcHJvdmlkZXIiOiJwYXNzd29yZCJ9fQ.POr8U2pP4XtTJEDRJ_BveRkCs92CIfDDdfU24OYe_aZh6LFPN0bQukNHXrLt3gaD30SUcg5mgmI2VUphgmwviMEGY-zizPC9o6GUKEEppZWQXfrfTyJNa1VKKH9h5zZPPFFW8UJRMi131pCc0ev26GS8Do-FJAgwHLJd6Jp2RbbqiCIeafNMhQCEoXohOk-VArNe7tPAb6-IjxqGVyNqvVyIo6niSXYvmgNjyF1WnnIw0CsnPoJlc5kVMtRdYeshJI7V117MOlUwZicF62vsm32eCunjn3qhN5XsujI7gy9us3vzwhdR1lxISZCLhLOXEYHPL373HJh7I_KN1C3NuA`;
const { jobs } = await client.request(
gql`
query GET_JOBS($bodyshopids: [uuid!]!) {

11095
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -17,6 +17,7 @@
"start": "node server.js"
},
"dependencies": {
"@aws-sdk/client-secrets-manager": "^3.388.0",
"@aws-sdk/credential-provider-node": "^3.319.0",
"@opensearch-project/opensearch": "^2.2.1",
"aws-sdk": "^2.1326.0",

View File

@@ -44,13 +44,13 @@ const io = new Server(server, {
});
exports.io = io;
require("./server/web-sockets/web-socket");
//app.use(fb.validateFirebaseIdToken);
// app.set('trust proxy', true)
// app.use(fb.validateFirebaseIdToken);
app.use(compression());
app.use(cookieParser());
app.use(bodyParser.json({ limit: "50mb" }));
app.use(bodyParser.urlencoded({ limit: "50mb", extended: true }));
//app.use(enforce.HTTPS({ trustProtoHeader: true }));
// app.use(enforce.HTTPS({ trustProtoHeader: true }));
app.use(
cors({ credentials: true, exposedHeaders: ["set-cookie"] })
// cors({
@@ -73,10 +73,17 @@ app.get("/test", async function (req, res) {
const commit = require("child_process").execSync(
"git rev-parse --short HEAD"
);
// console.log(app.get('trust proxy'));
// console.log("remoteAddress", req.socket.remoteAddress);
// console.log("X-Forwarded-For", req.header('x-forwarded-for'));
logger.log("test-api-status", "DEBUG", "api", { commit });
// sendEmail.sendServerEmail({
// subject: `API Check - ${process.env.NODE_ENV}`,
// text: `Server API check has come in. Remote IP: ${req.socket.remoteAddress}, X-Forwarded-For: ${req.header('x-forwarded-for')}`,
// });
sendEmail.sendServerEmail({
subject: `API Check - ${process.env.NODE_ENV}`,
text: `Server API check has come in. `,
text: `Server API check has come in.`,
});
res.status(200).send(`OK - ${commit}`);
});
@@ -222,6 +229,7 @@ app.post("/qbo/payments", fb.validateFirebaseIdToken, qbo.payments);
var data = require("./server/data/data");
app.post("/data/ah", data.autohouse);
app.post("/data/cc", data.claimscorp);
app.post("/record-handler/arms", data.arms);
var taskHandler = require("./server/tasks/tasks");

View File

@@ -643,6 +643,7 @@ async function InsertAccountPostingData(socket) {
wips.push(item);
});
socket.transWips = wips;
const { data: AccountPostingChange } = await axios.post(
PBS_ENDPOINTS.AccountingPostingChange,
@@ -697,6 +698,7 @@ async function MarkJobExported(socket, jobid) {
jobid: jobid,
successful: true,
useremail: socket.user.email,
metadata: socket.transWips,
},
bill: {
exported: true,

View File

@@ -8,8 +8,7 @@ require("dotenv").config({
function urlBuilder(realmId, object, query = null) {
return `https://${
//process.env.NODE_ENV === "production" ? "" :
"sandbox-"
process.env.NODE_ENV === "production" ? "" : "sandbox-"
}quickbooks.api.intuit.com/v3/company/${realmId}/${object}${
query ? `?query=${encodeURIComponent(query)}` : ""
}`;

847
server/data/claimscorp.js Normal file
View File

@@ -0,0 +1,847 @@
const path = require("path");
const queries = require("../graphql-client/queries");
const Dinero = require("dinero.js");
const moment = require("moment-timezone");
var builder = require("xmlbuilder2");
const _ = require("lodash");
const logger = require("../utils/logger");
const fs = require("fs");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
});
let Client = require("ssh2-sftp-client");
const client = require("../graphql-client/graphql-client").client;
const { sendServerEmail } = require("../email/sendemail");
const CCDineroFormat = "0,0.00";
const AhDateFormat = "MMDDYYYY";
const repairOpCodes = ["OP4", "OP9", "OP10"];
const replaceOpCodes = ["OP2", "OP5", "OP11", "OP12"];
const ftpSetup = {
host: process.env.CLAIMSCORP_HOST,
port: process.env.CLAIMSCORP_PORT,
username: process.env.CLAIMSCORP_USER,
password: process.env.CLAIMSCORP_PASSWORD,
debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data),
algorithms: {
serverHostKey: ["ssh-rsa", "ssh-dss"],
},
};
exports.default = async (req, res) => {
//Query for the List of Bodyshop Clients.
logger.log("claimscorp-start", "DEBUG", "api", null, null);
const { bodyshops } = await client.request(queries.GET_CLAIMSCORP_SHOPS);
const specificShopIds = req.body.bodyshopIds; // ['uuid]
const { start, end, skipUpload } = req.body; //YYYY-MM-DD
if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) {
res.sendStatus(401);
return;
}
const allxmlsToUpload = [];
const allErrors = [];
try {
for (const bodyshop of specificShopIds
? bodyshops.filter((b) => specificShopIds.includes(b.id))
: bodyshops) {
logger.log("claimscorp-start-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname,
});
const erroredJobs = [];
try {
const { jobs, bodyshops_by_pk } = await client.request(
queries.CLAIMSCORP_QUERY,
{
bodyshopid: bodyshop.id,
start: start
? moment(start).startOf("day")
: moment().subtract(5, "days").startOf("day"),
...(end && { end: moment(end).startOf("day") }),
}
);
const claimsCorpObject = {
ClaimsCorpExport: {
ShopID: bodyshops_by_pk.claimscorpid,
ShopName: bodyshops_by_pk.shopname,
RO: jobs.map((j) =>
CreateRepairOrderTag(
{ ...j, bodyshop: bodyshops_by_pk },
function ({ job, error }) {
erroredJobs.push({ job: job, error: error.toString() });
}
)
),
},
};
if (erroredJobs.length > 0) {
logger.log("claimscorp-failed-jobs", "ERROR", "api", bodyshop.id, {
count: erroredJobs.length,
jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number)),
});
}
var ret = builder
.create(
{
// version: "1.0",
// encoding: "UTF-8",
//keepNullNodes: true,
},
claimsCorpObject
)
.end({ allowEmptyTags: true });
allxmlsToUpload.push({
count: claimsCorpObject.ClaimsCorpExport.RO.length,
xml: ret,
filename: `${bodyshop.claimscorpid}-MIS-${moment().format(
"YYYYMMDDTHHMMss"
)}.xml`,
});
logger.log("claimscorp-end-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname,
});
} catch (error) {
//Error at the shop level.
logger.log("claimscorp-error-shop", "ERROR", "api", bodyshop.id, {
...error,
});
allErrors.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
claimscorpid: bodyshop.claimscorpid,
fatal: true,
errors: [error.toString()],
});
} finally {
allErrors.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
claimscorpid: bodyshop.claimscorpid,
errors: erroredJobs.map((ej) => ({
ro_number: ej.job?.ro_number,
jobid: ej.job?.id,
error: ej.error,
})),
});
}
}
if (skipUpload) {
for (const xmlObj of allxmlsToUpload) {
fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml);
}
res.json(allxmlsToUpload);
sendServerEmail({
subject: `ClaimsCorp Report ${moment().format("MM-DD-YY")}`,
text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
Uploaded: ${JSON.stringify(
allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
null,
2
)}
`,
});
return;
}
let sftp = new Client();
sftp.on("error", (errors) =>
logger.log("claimscorp-sftp-error", "ERROR", "api", null, {
...errors,
})
);
try {
//Connect to the FTP and upload all.
await sftp.connect(ftpSetup);
for (const xmlObj of allxmlsToUpload) {
logger.log("claimscorp-sftp-upload", "DEBUG", "api", null, {
filename: xmlObj.filename,
});
const uploadResult = await sftp.put(
Buffer.from(xmlObj.xml),
`/${xmlObj.filename}`
);
logger.log("claimscorp-sftp-upload-result", "DEBUG", "api", null, {
uploadResult,
});
}
//***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml
} catch (error) {
logger.log("claimscorp-sftp-error", "ERROR", "api", null, {
...error,
});
} finally {
sftp.end();
}
sendServerEmail({
subject: `ClaimsCorp Report ${moment().format("MM-DD-YY")}`,
text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
Uploaded: ${JSON.stringify(
allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
null,
2
)}
`,
});
res.sendStatus(200);
} catch (error) {
res.status(200).json(error);
}
};
const CreateRepairOrderTag = (job, errorCallback) => {
//Level 2
if (!job.job_totals) {
errorCallback({
jobid: job.id,
job: job,
ro_number: job.ro_number,
error: { toString: () => "No job totals for RO." },
});
return {};
}
const repairCosts = CreateCosts(job);
//Calculate detail only lines.
const detailAdjustments = job.joblines
.filter((jl) => jl.ah_detail_line && jl.mod_lbr_ty)
.reduce(
(acc, val) => {
return {
hours: acc.hours + val.mod_lb_hrs,
amount: acc.amount.add(
Dinero({
amount: Math.round(
(job.job_totals.rates[val.mod_lbr_ty.toLowerCase()].rate || 0) *
val.mod_lb_hrs *
100
),
})
),
};
},
{ hours: 0, amount: Dinero() }
);
try {
const ret = {
RONumber: job.ro_number,
Customer: {
CustomerZip: (job.ownr_zip && job.ownr_zip.substring(0, 3)) || "",
CustomerState: job.ownr_st || "",
},
Vehicle: {
Year: job.v_model_yr
? parseInt(job.v_model_yr.match(/\d/g))
? parseInt(job.v_model_yr.match(/\d/g).join(""), 10)
: ""
: "",
Make: job.v_make_desc || "",
Model: job.v_model_desc || "",
BodyStyle: (job.vehicle && job.vehicle.v_bstyle) || "",
Color: job.v_color || "",
VIN: job.v_vin || "",
},
Carrier: {
InsuranceCo: job.ins_co_nm || "",
CompanyName: job.ins_co_nm || "",
},
Claim: job.clm_no || "",
Contacts: {
PC: job.employee_csr_rel
? `${
job.employee_csr_rel.last_name
? job.employee_csr_rel.last_name
: ""
}${job.employee_csr_rel.last_name ? ", " : ""}${
job.employee_csr_rel.first_name
? job.employee_csr_rel.first_name
: ""
}`
: "",
Phone1: "",
Phone2: "",
EstimatorName: `${job.est_ct_ln ? job.est_ct_ln : ""}${
job.est_ct_ln ? ", " : ""
}${job.est_ct_fn ? job.est_ct_fn : ""}`,
BodyTechnician: job.employee_body_rel
? `${
job.employee_body_rel.last_name
? job.employee_body_rel.last_name
: ""
}${job.employee_body_rel.last_name ? ", " : ""}${
job.employee_body_rel.first_name
? job.employee_body_rel.first_name
: ""
}`
: "",
PaintTechnician: job.employee_refinish_rel
? `${
job.employee_refinish_rel.last_name
? job.employee_refinish_rel.last_name
: ""
}${job.employee_refinish_rel.last_name ? ", " : ""}${
job.employee_refinish_rel.first_name
? job.employee_refinish_rel.first_name
: ""
}`
: "",
},
Dates: {
DateCreated:
(job.date_estimated &&
moment(job.date_estimated).format(AhDateFormat)) ||
"",
DateofLoss:
(job.loss_date && moment(job.loss_date).format(AhDateFormat)) || "",
DateFNOL: "",
DateContact: "",
DateEstimated:
(job.date_estimated &&
moment(job.date_estimated).format(AhDateFormat)) ||
"",
DateScheduled:
(job.scheduled_in &&
moment(job.scheduled_in)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
DateArrived:
(job.actual_in &&
moment(job.actual_in)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
DateFirstPartsOrdered:
(job.parts_orders &&
job.parts_orders[0] &&
moment(job.parts_orders[0].created_at)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
StartDate: job.date_repairstarted
? (job.date_repairstarted &&
moment(job.date_repairstarted)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
""
: (job.date_repairstarted &&
moment(job.actual_in)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
BodyStart: "",
BodyEnd: "",
FrameStart: "",
FrameEnd: "",
PrepStart: "",
PrepEnd: "",
SprayStart: "",
SprayEnd: "",
DateReady:
(job.actual_completion &&
moment(job.actual_completion)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
DateScheduledDelivery:
(job.scheduled_delivery &&
moment(job.scheduled_delivery)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
DateDelivered:
(job.actual_delivery &&
moment(job.actual_delivery)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
DateClosed:
(job.date_invoiced &&
moment(job.date_invoiced)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
BilledDate: "",
PaidInFullDate: "",
RoStatus: job.tlos_ind
? "TOT"
: StatusMapping(job.status, job.bodyshop.md_ro_statuses),
},
Sales: {
Body: Dinero(job.job_totals.rates.lab.total)
.add(Dinero(job.job_totals.rates.laa.total))
.add(Dinero(job.job_totals.rates.lad.total))
.add(Dinero(job.job_totals.rates.las.total))
.toFormat(CCDineroFormat),
Refinish: Dinero(job.job_totals.rates.lar.total).toFormat(
CCDineroFormat
),
Prep: Dinero().toFormat(CCDineroFormat),
Frame: Dinero(job.job_totals.rates.laf.total).toFormat(CCDineroFormat),
Mechanical: Dinero(job.job_totals.rates.lam.total).toFormat(
CCDineroFormat
),
Glass: Dinero(job.job_totals.rates.lag.total).toFormat(CCDineroFormat),
Elec: Dinero(job.job_totals.rates.lae.total).toFormat(CCDineroFormat),
Detail: detailAdjustments.amount.toFormat(CCDineroFormat),
Reassem: Dinero().toFormat(CCDineroFormat),
OtherLabor: Dinero(job.job_totals.rates.la1.total)
.add(Dinero(job.job_totals.rates.la2.total))
.add(Dinero(job.job_totals.rates.la3.total))
.add(Dinero(job.job_totals.rates.la4.total))
.add(Dinero(job.job_totals.rates.lau.total))
.subtract(detailAdjustments.amount)
.toFormat(CCDineroFormat),
BMatl: Dinero(job.job_totals.rates.mash.total).toFormat(CCDineroFormat),
PMatl: Dinero(job.job_totals.rates.mapa.total).toFormat(CCDineroFormat),
OEM: Dinero(
job.job_totals.parts.parts.list.PAN &&
job.job_totals.parts.parts.list.PAN.total
)
.add(
Dinero(
job.job_totals.parts.parts.list.PAP &&
job.job_totals.parts.parts.list.PAP.total
)
)
.toFormat(CCDineroFormat),
LKQ: Dinero(
job.job_totals.parts.parts.list.PAL &&
job.job_totals.parts.parts.list.PAL.total
).toFormat(CCDineroFormat),
AM: Dinero(
job.job_totals.parts.parts.list.PAA &&
job.job_totals.parts.parts.list.PAA.total
).toFormat(CCDineroFormat),
MechParts: Dinero().toFormat(CCDineroFormat),
OtherParts: Dinero(
job.job_totals.parts.parts.list.PAO &&
job.job_totals.parts.parts.list.PAO.total
).toFormat(CCDineroFormat),
OtherSales: Dinero(job.job_totals.additional.storage).toFormat(
CCDineroFormat
),
Sublet: Dinero(job.job_totals.parts.sublets.total).toFormat(
CCDineroFormat
),
Towing: Dinero(job.job_totals.additional.towing).toFormat(
CCDineroFormat
),
Rental:
job.job_totals.additional.additionalCostItems.includes(
"ATS Amount"
) === true
? Dinero(
job.job_totals.additional.additionalCostItems[
job.job_totals.additional.additionalCostItems.indexOf(
"ATS Amount"
)
].total
).toFormat(CCDineroFormat)
: Dinero().toFormat(CCDineroFormat),
HazWaste: Dinero().toFormat(CCDineroFormat),
Discounts: Dinero(job.job_totals.additional.adjustments).toFormat(
CCDineroFormat
),
Tax: Dinero(job.job_totals.totals.local_tax)
.add(Dinero(job.job_totals.totals.state_tax))
.add(Dinero(job.job_totals.totals.federal_tax))
.add(Dinero(job.job_totals.additional.pvrt))
.toFormat(CCDineroFormat),
NetSaleTotal: Dinero(job.job_totals.totals.subtotal).toFormat(
CCDineroFormat
),
SaleTotal: Dinero(job.job_totals.totals.total_repairs).toFormat(
CCDineroFormat
),
},
SaleHours: {
Body: job.job_totals.rates.lab.hours.toFixed(2),
BodyRepairHours: job.joblines
.filter((line) => repairOpCodes.includes(line.lbr_op))
.reduce((acc, val) => acc + val.mod_lb_hrs, 0)
.toFixed(2),
BodyReplaceHours: job.joblines
.filter((line) => replaceOpCodes.includes(line.lbr_op))
.reduce((acc, val) => acc + val.mod_lb_hrs, 0)
.toFixed(2),
Paint: job.job_totals.rates.lar.hours.toFixed(2),
Prep: "0.00",
FrameHours: job.job_totals.rates.laf.hours.toFixed(2),
MechanicalHours: job.job_totals.rates.lam.hours.toFixed(2),
GlassHours: job.job_totals.rates.lag.hours.toFixed(2),
ElectricalHours: job.job_totals.rates.lae.hours.toFixed(2),
DetailHours: detailAdjustments.hours,
Reassem: "0.00",
Other: (
job.job_totals.rates.la1.hours +
job.job_totals.rates.la2.hours +
job.job_totals.rates.la3.hours +
job.job_totals.rates.la4.hours +
job.job_totals.rates.lau.hours -
detailAdjustments.hours
).toFixed(2),
TotalHours: job.joblines
.reduce((acc, val) => acc + val.mod_lb_hrs, 0)
.toFixed(2),
},
Costs: {
Body: repairCosts.BodyLaborTotalCost.toFormat(CCDineroFormat),
Paint: repairCosts.RefinishLaborTotalCost.toFormat(CCDineroFormat),
Prep: Dinero().toFormat(CCDineroFormat),
Frame: Dinero(job.job_totals.rates.laf.total).toFormat(CCDineroFormat),
Mech: repairCosts.MechanicalLaborTotalCost.toFormat(CCDineroFormat),
Glass: repairCosts.GlassLaborTotalCost.toFormat(CCDineroFormat),
Elec: repairCosts.ElectricalLaborTotalCost.toFormat(CCDineroFormat),
Detail: Dinero().toFormat(CCDineroFormat),
Reassem: Dinero().toFormat(CCDineroFormat),
OtherLabor: repairCosts.LaborMiscTotalCost.toFormat(CCDineroFormat),
Bmatl: repairCosts.BMTotalCost.toFormat(CCDineroFormat),
Pmatl: repairCosts.PMTotalCost.toFormat(CCDineroFormat),
OEM: repairCosts.PartsOemCost.toFormat(CCDineroFormat),
LKQ: repairCosts.PartsRecycledCost.toFormat(CCDineroFormat),
AM: repairCosts.PartsAMCost.toFormat(CCDineroFormat),
MechParts: Dinero().toFormat(CCDineroFormat),
OtherParts: Dinero().toFormat(CCDineroFormat), //Check Synergy
OtherCosts: repairCosts.PartsOtherCost.toFormat(CCDineroFormat),
Sublet: repairCosts.SubletTotalCost.toFormat(CCDineroFormat),
Towing: repairCosts.TowingTotalCost.toFormat(CCDineroFormat),
Storage: repairCosts.StorageTotalCost.toFormat(CCDineroFormat),
Rental: Dinero().toFormat(CCDineroFormat),
HazWaste: Dinero().toFormat(CCDineroFormat),
CostTotal: repairCosts.TotalCost.toFormat(CCDineroFormat),
},
CostHours: {
Body: repairCosts.BodyLaborTotalHrs.toFixed(2),
Paint: repairCosts.RefinishLaborTotalHrs.toFixed(2),
Prep: "0.00",
Frame: repairCosts.FrameLaborTotalHrs.toFixed(2),
Mech: repairCosts.MechanicalLaborTotalHrs.toFixed(2),
Glass: repairCosts.GlassLaborTotalHrs.toFixed(2),
Elec: repairCosts.ElectricalLaborTotalHrs.toFixed(2),
Detail: "0.00",
Other: repairCosts.LaborMiscTotalHrs.toFixed(2),
CostTotalHours: repairCosts.TotalHrs.toFixed(2),
},
};
return ret;
} catch (error) {
logger.log("claimscorp-job-calculate-error", "ERROR", "api", null, {
error,
});
errorCallback({ jobid: job.id, ro_number: job.ro_number, error });
}
};
const CreateCosts = (job) => {
//Create a mapping based on AH Requirements
//For DMS, the keys in the object below are the CIECA part types.
const billTotalsByCostCenters = job.bills.reduce((bill_acc, bill_val) => {
//At the bill level.
bill_val.billlines.map((line_val) => {
//At the bill line level.
if (!bill_acc[line_val.cost_center])
bill_acc[line_val.cost_center] = Dinero();
bill_acc[line_val.cost_center] = bill_acc[line_val.cost_center].add(
Dinero({
amount: Math.round((line_val.actual_cost || 0) * 100),
})
.multiply(line_val.quantity)
.multiply(bill_val.is_credit_memo ? -1 : 1)
);
return null;
});
return bill_acc;
}, {});
//If the hourly rates for job costing are set, add them in.
if (
job.bodyshop.jc_hourly_rates &&
(job.bodyshop.jc_hourly_rates.mapa ||
typeof job.bodyshop.jc_hourly_rates.mapa === "number" ||
isNaN(job.bodyshop.jc_hourly_rates.mapa) === false)
) {
if (
!billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
]
)
billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
] = Dinero();
if (job.bodyshop.use_paint_scale_data === true) {
if (job.mixdata.length > 0) {
billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
] = Dinero({
amount: Math.round(
((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) * 100
),
});
} else {
billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
] = billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
].add(
Dinero({
amount: Math.round(
(job.bodyshop.jc_hourly_rates &&
job.bodyshop.jc_hourly_rates.mapa * 100) ||
0
),
}).multiply(job.job_totals.rates.mapa.hours)
);
}
} else {
billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
] = billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
].add(
Dinero({
amount: Math.round(
(job.bodyshop.jc_hourly_rates &&
job.bodyshop.jc_hourly_rates.mapa * 100) ||
0
),
}).multiply(job.job_totals.rates.mapa.hours)
);
}
}
if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mash) {
if (
!billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
]
)
billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
] = Dinero();
billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
] = billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
].add(
Dinero({
amount: Math.round(
(job.bodyshop.jc_hourly_rates &&
job.bodyshop.jc_hourly_rates.mash * 100) ||
0
),
}).multiply(job.job_totals.rates.mash.hours)
);
}
//Uses CIECA Labor types.
const ticketTotalsByCostCenter = job.timetickets.reduce(
(ticket_acc, ticket_val) => {
//At the invoice level.
if (!ticket_acc[ticket_val.cost_center])
ticket_acc[ticket_val.cost_center] = Dinero();
ticket_acc[ticket_val.cost_center] = ticket_acc[
ticket_val.cost_center
].add(
Dinero({
amount: Math.round((ticket_val.rate || 0) * 100),
}).multiply(
(ticket_val.flat_rate
? ticket_val.productivehrs
: ticket_val.actualhrs) || 0
)
);
return ticket_acc;
},
{}
);
const ticketHrsByCostCenter = job.timetickets.reduce(
(ticket_acc, ticket_val) => {
//At the invoice level.
if (!ticket_acc[ticket_val.cost_center])
ticket_acc[ticket_val.cost_center] = 0;
ticket_acc[ticket_val.cost_center] =
ticket_acc[ticket_val.cost_center] +
(ticket_val.flat_rate
? ticket_val.productivehrs
: ticket_val.actualhrs) || 0;
return ticket_acc;
},
{}
);
//CIECA STANDARD MAPPING OBJECT.
const ciecaObj = {
ATS: "ATS",
LA1: "LA1",
LA2: "LA2",
LA3: "LA3",
LA4: "LA4",
LAA: "LAA",
LAB: "LAB",
LAD: "LAD",
LAE: "LAE",
LAF: "LAF",
LAG: "LAG",
LAM: "LAM",
LAR: "LAR",
LAS: "LAS",
LAU: "LAU",
PAA: "PAA",
PAC: "PAC",
PAG: "PAG",
PAL: "PAL",
PAM: "PAM",
PAN: "PAN",
PAO: "PAO",
PAP: "PAP",
PAR: "PAR",
PAS: "PAS",
TOW: "TOW",
MAPA: "MAPA",
MASH: "MASH",
PASL: "PASL",
};
const defaultCosts =
job.bodyshop.cdk_dealerid || job.bodyshop.pbs_serialnumber
? ciecaObj
: job.bodyshop.md_responsibility_centers.defaults.costs;
return {
PartsTotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => {
if (
key !== defaultCosts.PAS &&
key !== defaultCosts.PASL &&
key !== defaultCosts.MAPA &&
key !== defaultCosts.MASH &&
key !== defaultCosts.TOW
)
return acc.add(billTotalsByCostCenters[key]);
return acc;
}, Dinero()),
PartsOemCost: (billTotalsByCostCenters[defaultCosts.PAN] || Dinero()).add(
billTotalsByCostCenters[defaultCosts.PAP] || Dinero()
),
PartsAMCost: billTotalsByCostCenters[defaultCosts.PAA] || Dinero(),
PartsReconditionedCost:
billTotalsByCostCenters[defaultCosts.PAM] || Dinero(),
PartsRecycledCost: billTotalsByCostCenters[defaultCosts.PAL] || Dinero(),
PartsOtherCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(),
SubletTotalCost:
billTotalsByCostCenters[defaultCosts.PAS] ||
Dinero(billTotalsByCostCenters[defaultCosts.PASL] || Dinero()),
BodyLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAB] || Dinero(),
BodyLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAB] || 0,
RefinishLaborTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAR] || Dinero(),
RefinishLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAR] || 0,
MechanicalLaborTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAM] || Dinero(),
MechanicalLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAM] || 0,
StructuralLaborTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAS] || Dinero(),
StructuralLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAS] || 0,
ElectricalLaborTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAE] || Dinero(),
ElectricalLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAE] || 0,
FrameLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAF] || Dinero(),
FrameLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAF] || 0,
GlassLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAG] || Dinero(),
GlassLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAG] || 0,
DetailLaborTotalCost: Dinero(),
// ticketTotalsByCostCenter[defaultCosts.LAD] || Dinero(),
LaborMiscTotalCost: (ticketTotalsByCostCenter[defaultCosts.LA1] || Dinero())
.add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero())
.add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero())
.add(ticketTotalsByCostCenter[defaultCosts.LA3] || Dinero())
.add(ticketTotalsByCostCenter[defaultCosts.LA4] || Dinero())
.add(ticketTotalsByCostCenter[defaultCosts.LAU] || Dinero()),
LaborMiscTotalHrs:
(ticketHrsByCostCenter[defaultCosts.LA1] || 0) +
(ticketHrsByCostCenter[defaultCosts.LA2] || 0) +
(ticketHrsByCostCenter[defaultCosts.LA3] || 0) +
(ticketHrsByCostCenter[defaultCosts.LA4] || 0) +
(ticketHrsByCostCenter[defaultCosts.LAU] || 0),
PMTotalCost: billTotalsByCostCenters[defaultCosts.MAPA] || Dinero(),
BMTotalCost: billTotalsByCostCenters[defaultCosts.MASH] || Dinero(),
MiscTotalCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(),
TowingTotalCost: billTotalsByCostCenters[defaultCosts.TOW] || Dinero(),
StorageTotalCost: Dinero(),
DetailTotal: Dinero(),
DetailTotalCost: Dinero(),
SalesTaxTotalCost: Dinero(),
LabourTotalCost: Object.keys(ticketTotalsByCostCenter).reduce(
(acc, key) => {
return acc.add(ticketTotalsByCostCenter[key]);
},
Dinero()
),
TotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => {
return acc.add(billTotalsByCostCenters[key]);
}, Dinero()),
TotalHrs: job.timetickets.reduce((acc, ticket_val) => {
return (
acc +
(ticket_val.flat_rate
? ticket_val.productivehrs
: ticket_val.actualhrs) || 0
);
}, 0),
};
};
const StatusMapping = (status, md_ro_statuses) => {
//Possible return statuses CLO, CAN, OPN
const {
default_imported,
default_open,
default_scheduled,
default_arrived,
default_completed,
default_delivered,
default_invoiced,
default_exported,
default_void,
} = md_ro_statuses;
if (
status === default_open ||
status === default_imported ||
status === default_scheduled ||
status === default_arrived ||
status === default_completed ||
status === default_delivered ||
md_ro_statuses.production_statuses.includes(status)
)
return "OPN";
else if (status === default_invoiced || status === default_exported)
return "CLO";
else if (status === default_void) return "CAN";
else return "UNDEFINED";
};

View File

@@ -1,2 +1,3 @@
exports.autohouse = require("./autohouse").default;
exports.arms = require("./arms").default;
exports.claimscorp = require("./claimscorp").default;
exports.arms = require("./arms").default;

View File

@@ -841,6 +841,179 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshop
}
`;
exports.CLAIMSCORP_QUERY = `query CLAIMSCORP_EXPORT($start: timestamptz, $bodyshopid: uuid!, $end: timestamptz) {
bodyshops_by_pk(id: $bodyshopid){
id
shopname
address1
city
state
zip_post
country
phone
md_ro_statuses
md_order_statuses
claimscorpid
md_responsibility_centers
jc_hourly_rates
cdk_dealerid
pbs_serialnumber
use_paint_scale_data
timezone
}
jobs(where: {_and: [{converted: {_eq: true}}, {updated_at: {_gt: $start}}, {updated_at: {_lte: $end}}, {shopid: {_eq: $bodyshopid}}]}) {
id
created_at
ro_number
status
est_ct_fn
est_ct_ln
ownr_st
ownr_zip
tlos_ind
v_color
v_model_yr
v_model_desc
v_make_desc
v_vin
vehicle {
v_bstyle
}
ins_co_nm
clm_no
loss_date
asgn_date
date_estimated
date_open
scheduled_in
actual_in
scheduled_completion
actual_completion
scheduled_delivery
actual_delivery
date_invoiced
date_exported
rate_la1
rate_la2
rate_la3
rate_la4
rate_laa
rate_lab
rate_lad
rate_lae
rate_laf
rate_lag
rate_lam
rate_lar
rate_las
rate_lau
rate_ma2s
rate_ma2t
rate_ma3s
rate_mabl
rate_macs
rate_mahw
rate_matd
rate_mapa
rate_mash
job_totals
parts_tax_rates
date_repairstarted
joblines(where: {removed: {_eq: false}}) {
id
line_no
line_ind
status
line_ind
db_price
act_price
mod_lb_hrs
mod_lbr_ty
line_desc
prt_dsmk_m
prt_dsmk_p
part_qty
part_type
oem_partno
lbr_op
profitcenter_part
profitcenter_labor
ah_detail_line
parts_order_lines(order_by: {parts_order: {order_date: desc_nulls_last}} limit: 1){
parts_order{
id
order_date
}
}
billlines(order_by: {bill: {date: desc_nulls_last}} limit: 1) {
actual_cost
actual_price
quantity
bill {
vendor {
name
}
invoice_number
date
}
}
}
bills {
id
federal_tax_rate
local_tax_rate
state_tax_rate
is_credit_memo
billlines {
actual_cost
cost_center
id
quantity
}
}
employee_body_rel {
first_name
last_name
employee_number
id
}
employee_csr_rel {
first_name
last_name
employee_number
id
}
employee_prep_rel {
first_name
last_name
employee_number
id
}
employee_refinish_rel {
first_name
last_name
employee_number
id
}
parts_orders(limit: 1, order_by: {created_at: desc}) {
created_at
}
timetickets {
id
rate
cost_center
actualhrs
productivehrs
flat_rate
}
mixdata(limit: 1, order_by: {updated_at: desc}) {
jobid
totalliquidcost
}
}
}
`;
exports.ENTEGRAL_EXPORT = `
query ENTEGRAL_EXPORT($bodyshopid: uuid!) {
jobs(where: {_and: [{converted: {_eq: true}}, {shopid: {_eq: $bodyshopid}}]}) {
@@ -992,7 +1165,9 @@ exports.GET_JOB_BY_PK = `query GET_JOB_BY_PK($id: uuid!) {
ins_ph1
est_co_nm
est_ct_fn
shopid
est_ct_ln
cieca_pfl
vehicle{
id
notes
@@ -1389,6 +1564,27 @@ exports.GET_AUTOHOUSE_SHOPS = `query GET_AUTOHOUSE_SHOPS {
}
`;
exports.GET_CLAIMSCORP_SHOPS = `query GET_CLAIMSCORP_SHOPS {
bodyshops(where: {claimscorpid: {_is_null: false}}){
id
shopname
address1
city
state
zip_post
country
phone
md_ro_statuses
md_order_statuses
claimscorpid
md_responsibility_centers
jc_hourly_rates
imexshopid
timezone
}
}
`;
exports.GET_ENTEGRAL_SHOPS = `query GET_AUTOHOUSE_SHOPS {
bodyshops(where: {entegral_id: {_is_null: false}}){
id

View File

@@ -4,6 +4,9 @@ const queries = require("../graphql-client/queries");
const Dinero = require("dinero.js");
const qs = require("query-string");
const axios = require("axios");
const moment = require("moment");
const logger = require("../utils/logger");
require("dotenv").config({
path: path.resolve(
process.cwd(),
@@ -12,7 +15,15 @@ require("dotenv").config({
});
const domain = process.env.NODE_ENV ? "secure" : "test";
const SecretsManager = require("./aws-secrets-manager");
const {
SecretsManagerClient,
GetSecretValueCommand,
} = require("@aws-sdk/client-secrets-manager");
const client = new SecretsManagerClient({
region: "ca-central-1",
});
const gqlClient = require("../graphql-client/graphql-client").client;
@@ -20,38 +31,55 @@ const getShopCredentials = async (bodyshop) => {
// Development only
if (process.env.NODE_ENV === undefined) {
return {
merchantkey: process.env.DEV_INTELLIPAY_MERCHANTKEY,
apikey: process.env.DEV_INTELLIPAY_APIKEY,
merchantkey: process.env.INTELLIPAY_MERCHANTKEY,
apikey: process.env.INTELLIPAY_APIKEY,
};
}
// Production code
if (bodyshop?.imexshopid) {
const secret = await SecretsManager.getSecret(
`intellipay-credentials-${bodyshop.imexshopid}`,
process.env.REGION
);
return JSON.parse(secret);
try {
const secret = await client.send(
new GetSecretValueCommand({
SecretId: `intellipay-credentials-${bodyshop.imexshopid}`,
VersionStage: "AWSCURRENT", // VersionStage defaults to AWSCURRENT if unspecified
})
);
return JSON.parse(secret.SecretString);
} catch (error) {
return {
error: error.message,
};
}
}
};
exports.lightbox_credentials = async (req, res) => {
logger.log(
"intellipay-lightbox-credentials",
"DEBUG",
req.user?.email,
null,
null
);
const shopCredentials = await getShopCredentials(req.body.bodyshop);
if (shopCredentials.error) {
res.json(shopCredentials);
return;
}
try {
const options = {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
//TODO: Move these to environment variables/database.
data: qs.stringify({
...shopCredentials,
operatingenv:
process.env.NODE_ENV === undefined
? process.env.NODE_ENV
: "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
};
const response = await axios(options);
@@ -59,11 +87,20 @@ exports.lightbox_credentials = async (req, res) => {
res.send(response.data);
} catch (error) {
console.log(error);
logger.log(
"intellipay-lightbox-credentials-error",
"ERROR",
req.user?.email,
null,
{ error: JSON.stringify(error) }
);
res.json({ error });
}
};
exports.payment_refund = async (req, res) => {
logger.log("intellipay-refund", "DEBUG", req.user?.email, null, null);
const shopCredentials = await getShopCredentials(req.body.bodyshop);
try {
@@ -85,11 +122,15 @@ exports.payment_refund = async (req, res) => {
res.send(response.data);
} catch (error) {
console.log(error);
logger.log("intellipay-refund-error", "ERROR", req.user?.email, null, {
error: JSON.stringify(error),
});
res.json({ error });
}
};
exports.generate_payment_url = async (req, res) => {
logger.log("intellipay-payment-url", "DEBUG", req.user?.email, null, null);
const shopCredentials = await getShopCredentials(req.body.bodyshop);
try {
const options = {
@@ -100,6 +141,7 @@ exports.generate_payment_url = async (req, res) => {
...shopCredentials,
...req.body,
createshorturl: true,
//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`,
};
@@ -109,76 +151,58 @@ exports.generate_payment_url = async (req, res) => {
res.send(response.data);
} catch (error) {
console.log(error);
logger.log("intellipay-payment-url-error", "ERROR", req.user?.email, null, {
error: JSON.stringify(error),
});
res.json({ error });
}
};
exports.postback = async (req, res) => {
console.log("postback as", req.body);
logger.log("intellipay-postback", "ERROR", req.user?.email, null, req.body);
const { body: values } = req;
if (!values.invoice) {
res.sendStatus(200);
return;
}
// TODO query job by account name
const job = await gqlClient.request(queries.GET_JOB_BY_RO_NUMBER, {
ro_number: values.account,
const job = await gqlClient.request(queries.GET_JOB_BY_PK, {
id: values.invoice,
});
// TODO add mutation to database
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
paymentInput: {
amount: values.total,
transactionid: `C00 ${values.authcode}`,
payer: "Customer",
type: values.cardtype,
jobid: job.jobs[0].id,
date: moment(Date.now()),
},
});
try {
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
paymentInput: {
amount: values.total,
transactionid: `C00 ${values.authcode}`,
payer: "Customer",
type: values.cardtype,
jobid: values.invoice,
date: moment(Date.now()),
},
});
await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, {
paymentResponse: {
amount: values.total,
bodyshopid: job.jobs[0].bodyshop.id,
paymentid: paymentResult.id,
jobid: job.jobs[0].id,
declinereason: "Approved",
ext_paymentid: values.paymentid,
successful: true,
response: values,
},
});
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,
},
});
res.send({ message: "Postback Successful" });
res.send({ message: "Postback Successful" });
} catch (error) {
logger.log("intellipay-postback-error", "ERROR", req.user?.email, null, {
error: JSON.stringify(error),
body: req.body,
});
res.status(400).json({ succesful: false, error: error.message });
}
};
`{
ipaddress: '136.158.34.242',
firstname: 'JC',
notes: '',
city: '',
fee: ' 0.00',
origin: 'OneLink',
total: '5061.36',
avsdata: 'N',
arglist: '""',
state: ' ',
cardtype: 'Visa',
department: '',
email: '',
timestamp: "{ts '2023-03-23 09:52:23'}",
op: 'Kh6Pa6AT9keg',
amount: '5061.36',
method: 'CARD',
address2: '',
address1: '',
lastname: 'Tolentino',
zipcode: '1742 ',
authcode: '367885',
phone: '',
merchantid: '7114',
paymentid: '24205435',
customerid: '19610104',
comment: '',
invoice: '',
account: 'QBD241'
}`;

View File

@@ -389,7 +389,7 @@ async function CalculateRatesTotals({ job, client }) {
lbr_op: "OP11",
lbr_amt: 0,
op_code_desc: "REMOVE / REPLACE",
tax_part: true,
tax_part: hasMahwLine.tax_amt > 0 ? true : false,
db_ref: null,
manual_line: true,
jobid: job.id,
@@ -399,6 +399,26 @@ async function CalculateRatesTotals({ job, client }) {
lineInput: [newMahwLine],
});
}
//Materials Scrubbing as required by CCC.
let matTotalLine = job.cieca_stl.data.find((l) => l.ttl_typecd === "MAT");
let shopMatLine = job.cieca_stl.data.find((l) => l.ttl_typecd === "MASH");
if (matTotalLine && shopMatLine) {
//Check to see if theyre different
let calcMapaAmount = Dinero({
amount: Math.round(
(matTotalLine?.ttl_amt - shopMatLine?.ttl_amt - stlMahw?.ttl_amt) * 100
),
});
let mapaDifference = calcMapaAmount.subtract(ret.mapa.total);
if (mapaDifference.getAmount() > 0) {
//fix it.
ret.mapa.total = calcMapaAmount;
//Add the difference to the subt total as well.
subtotal = subtotal.add(mapaDifference);
}
}
ret.subtotal = subtotal;
ret.rates_subtotal = rates_subtotal;
@@ -690,7 +710,7 @@ function CalculateAdditional(job) {
.reduce((acc, val) => {
const lineValue = Dinero({
amount: Math.round((val.act_price || 0) * 100),
}).multiply(val.part_qty || 1);
}).multiply(val.part_qty);
if (val.db_ref === "936004") {
//Shipping line IO-1921.
@@ -796,9 +816,43 @@ function CalculateTaxesTotals(job, otherTotals) {
)
);
}
console.log(statePartsTax.toFormat(), val.line_desc);
});
let laborTaxTotal = Dinero();
if (Object.keys(job.cieca_pfl).length > 0) {
//Do it by labor type
const types = [
"la1",
"la2",
"la3",
"la4",
"lau",
"laa",
"lab",
"lad",
"lae",
"laf",
"lag",
"lam",
"lar",
"las",
];
types.forEach((type) => {
laborTaxTotal = laborTaxTotal.add(
otherTotals.rates[type].total.percentage(
job.cieca_pfl[type.toUpperCase()]
? job.cieca_pfl[type.toUpperCase()].lbr_taxp
: (job.tax_lbr_rt || 0) * 100
)
);
});
} else {
//We don't have it, just add in how it was before.
laborTaxTotal = otherTotals.rates.subtotal.percentage(
(job.tax_lbr_rt || 0) * 100
); // THis is currently using the lbr tax rate from PFH not PFL.
}
let ret = {
subtotal: subtotal,
federal_tax: subtotal
@@ -810,9 +864,7 @@ function CalculateTaxesTotals(job, otherTotals) {
),
statePartsTax,
state_tax: statePartsTax
.add(
otherTotals.rates.subtotal.percentage((job.tax_lbr_rt || 0) * 100) // THis is currently using the lbr tax rate from PFH not PFL.
)
.add(laborTaxTotal)
.add(
otherTotals.additional.adjustments.percentage(
(job.tax_lbr_rt || 0) * 100
@@ -835,7 +887,7 @@ function CalculateTaxesTotals(job, otherTotals) {
.add(
otherTotals.rates.mash.hasMashLine === false //If parts and materials were not added as lines, we must calculate the taxes on them.
? otherTotals.rates.mash.total.percentage(
(job.tax_paint_mat_rt || 0) * 100
(job.tax_shop_mat_rt || 0) * 100
)
: Dinero()
),
@@ -882,18 +934,15 @@ function DiscountNotAlreadyCounted(jobline, joblines) {
(jobline.prt_dsmk_m / (jobline.act_price - jobline.prt_dsmk_m)) * 100
) === Math.abs(jobline.prt_dsmk_p)
) {
console.log(jobline.line_desc, "Already had the discount counted.");
return false;
}
//Check it against the database price too? If it's an OE part.
console.log(jobline.db_price - jobline.act_price);
if (
Math.abs(jobline.db_price - jobline.act_price) -
Math.abs(jobline.prt_dsmk_m) <
0.01
) {
console.log(jobline.line_desc, "Already had the discount counted.");
return false;
}

703
yarn.lock
View File

@@ -2,6 +2,15 @@
# yarn lockfile v1
"@aws-crypto/crc32@3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@aws-crypto/crc32/-/crc32-3.0.0.tgz#07300eca214409c33e3ff769cd5697b57fdd38fa"
integrity sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==
dependencies:
"@aws-crypto/util" "^3.0.0"
"@aws-sdk/types" "^3.222.0"
tslib "^1.11.1"
"@aws-crypto/ie11-detection@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz#640ae66b4ec3395cee6a8e94ebcd9f80c24cd688"
@@ -56,6 +65,49 @@
"@aws-sdk/types" "3.310.0"
tslib "^2.5.0"
"@aws-sdk/client-secrets-manager@^3.388.0":
version "3.388.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.388.0.tgz#284429ebc9376a167c3197075a848ea00b49bea9"
integrity sha512-XOf7FXz2Xn6tbykx/79rDLWysMLX5hQNciuCdbaHhKiflyTSYFNOpe5NQoq7jTzA64NW4dUxJUNwsBdo5M/i3g==
dependencies:
"@aws-crypto/sha256-browser" "3.0.0"
"@aws-crypto/sha256-js" "3.0.0"
"@aws-sdk/client-sts" "3.388.0"
"@aws-sdk/credential-provider-node" "3.388.0"
"@aws-sdk/middleware-host-header" "3.387.0"
"@aws-sdk/middleware-logger" "3.387.0"
"@aws-sdk/middleware-recursion-detection" "3.387.0"
"@aws-sdk/middleware-signing" "3.387.0"
"@aws-sdk/middleware-user-agent" "3.387.0"
"@aws-sdk/types" "3.387.0"
"@aws-sdk/util-endpoints" "3.387.0"
"@aws-sdk/util-user-agent-browser" "3.387.0"
"@aws-sdk/util-user-agent-node" "3.387.0"
"@smithy/config-resolver" "^2.0.2"
"@smithy/fetch-http-handler" "^2.0.2"
"@smithy/hash-node" "^2.0.2"
"@smithy/invalid-dependency" "^2.0.2"
"@smithy/middleware-content-length" "^2.0.2"
"@smithy/middleware-endpoint" "^2.0.2"
"@smithy/middleware-retry" "^2.0.2"
"@smithy/middleware-serde" "^2.0.2"
"@smithy/middleware-stack" "^2.0.0"
"@smithy/node-config-provider" "^2.0.2"
"@smithy/node-http-handler" "^2.0.2"
"@smithy/protocol-http" "^2.0.2"
"@smithy/smithy-client" "^2.0.2"
"@smithy/types" "^2.1.0"
"@smithy/url-parser" "^2.0.2"
"@smithy/util-base64" "^2.0.0"
"@smithy/util-body-length-browser" "^2.0.0"
"@smithy/util-body-length-node" "^2.0.0"
"@smithy/util-defaults-mode-browser" "^2.0.2"
"@smithy/util-defaults-mode-node" "^2.0.2"
"@smithy/util-retry" "^2.0.0"
"@smithy/util-utf8" "^2.0.0"
tslib "^2.5.0"
uuid "^8.3.2"
"@aws-sdk/client-sso-oidc@3.319.0":
version "3.319.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.319.0.tgz#d9c1045ac3e1c55b719590f2a47e825a803fd6ed"
@@ -132,6 +184,88 @@
"@aws-sdk/util-utf8" "3.310.0"
tslib "^2.5.0"
"@aws-sdk/client-sso@3.387.0":
version "3.387.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.387.0.tgz#d2182c09ad8d75a1a8896c2765e6f8729118660f"
integrity sha512-E7uKSvbA0XMKSN5KLInf52hmMpe9/OKo6N9OPffGXdn3fNEQlvyQq3meUkqG7Is0ldgsQMz5EUBNtNybXzr3tQ==
dependencies:
"@aws-crypto/sha256-browser" "3.0.0"
"@aws-crypto/sha256-js" "3.0.0"
"@aws-sdk/middleware-host-header" "3.387.0"
"@aws-sdk/middleware-logger" "3.387.0"
"@aws-sdk/middleware-recursion-detection" "3.387.0"
"@aws-sdk/middleware-user-agent" "3.387.0"
"@aws-sdk/types" "3.387.0"
"@aws-sdk/util-endpoints" "3.387.0"
"@aws-sdk/util-user-agent-browser" "3.387.0"
"@aws-sdk/util-user-agent-node" "3.387.0"
"@smithy/config-resolver" "^2.0.2"
"@smithy/fetch-http-handler" "^2.0.2"
"@smithy/hash-node" "^2.0.2"
"@smithy/invalid-dependency" "^2.0.2"
"@smithy/middleware-content-length" "^2.0.2"
"@smithy/middleware-endpoint" "^2.0.2"
"@smithy/middleware-retry" "^2.0.2"
"@smithy/middleware-serde" "^2.0.2"
"@smithy/middleware-stack" "^2.0.0"
"@smithy/node-config-provider" "^2.0.2"
"@smithy/node-http-handler" "^2.0.2"
"@smithy/protocol-http" "^2.0.2"
"@smithy/smithy-client" "^2.0.2"
"@smithy/types" "^2.1.0"
"@smithy/url-parser" "^2.0.2"
"@smithy/util-base64" "^2.0.0"
"@smithy/util-body-length-browser" "^2.0.0"
"@smithy/util-body-length-node" "^2.0.0"
"@smithy/util-defaults-mode-browser" "^2.0.2"
"@smithy/util-defaults-mode-node" "^2.0.2"
"@smithy/util-retry" "^2.0.0"
"@smithy/util-utf8" "^2.0.0"
tslib "^2.5.0"
"@aws-sdk/client-sts@3.388.0":
version "3.388.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.388.0.tgz#df4363f89de34bd02533056fc335ec8e785f788c"
integrity sha512-y9FAcAYHT8O6T/jqhgsIQUb4gLiSTKD3xtzudDvjmFi8gl0oRIY1npbeckSiK6k07VQugm2s64I0nDnDxtWsBg==
dependencies:
"@aws-crypto/sha256-browser" "3.0.0"
"@aws-crypto/sha256-js" "3.0.0"
"@aws-sdk/credential-provider-node" "3.388.0"
"@aws-sdk/middleware-host-header" "3.387.0"
"@aws-sdk/middleware-logger" "3.387.0"
"@aws-sdk/middleware-recursion-detection" "3.387.0"
"@aws-sdk/middleware-sdk-sts" "3.387.0"
"@aws-sdk/middleware-signing" "3.387.0"
"@aws-sdk/middleware-user-agent" "3.387.0"
"@aws-sdk/types" "3.387.0"
"@aws-sdk/util-endpoints" "3.387.0"
"@aws-sdk/util-user-agent-browser" "3.387.0"
"@aws-sdk/util-user-agent-node" "3.387.0"
"@smithy/config-resolver" "^2.0.2"
"@smithy/fetch-http-handler" "^2.0.2"
"@smithy/hash-node" "^2.0.2"
"@smithy/invalid-dependency" "^2.0.2"
"@smithy/middleware-content-length" "^2.0.2"
"@smithy/middleware-endpoint" "^2.0.2"
"@smithy/middleware-retry" "^2.0.2"
"@smithy/middleware-serde" "^2.0.2"
"@smithy/middleware-stack" "^2.0.0"
"@smithy/node-config-provider" "^2.0.2"
"@smithy/node-http-handler" "^2.0.2"
"@smithy/protocol-http" "^2.0.2"
"@smithy/smithy-client" "^2.0.2"
"@smithy/types" "^2.1.0"
"@smithy/url-parser" "^2.0.2"
"@smithy/util-base64" "^2.0.0"
"@smithy/util-body-length-browser" "^2.0.0"
"@smithy/util-body-length-node" "^2.0.0"
"@smithy/util-defaults-mode-browser" "^2.0.2"
"@smithy/util-defaults-mode-node" "^2.0.2"
"@smithy/util-retry" "^2.0.0"
"@smithy/util-utf8" "^2.0.0"
fast-xml-parser "4.2.5"
tslib "^2.5.0"
"@aws-sdk/config-resolver@3.310.0":
version "3.310.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/config-resolver/-/config-resolver-3.310.0.tgz#c02dce96546d5cd25551bc89907b27224e16ca7f"
@@ -151,6 +285,16 @@
"@aws-sdk/types" "3.310.0"
tslib "^2.5.0"
"@aws-sdk/credential-provider-env@3.387.0":
version "3.387.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.387.0.tgz#7323eada10228c0157195a922d10638cd65c293c"
integrity sha512-PVqNk7XPIYe5CMYNvELkcALtkl/pIM8/uPtqEtTg+mgnZBeL4fAmgXZiZMahQo1DxP5t/JaK384f6JG+A0qDjA==
dependencies:
"@aws-sdk/types" "3.387.0"
"@smithy/property-provider" "^2.0.0"
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@aws-sdk/credential-provider-imds@3.310.0":
version "3.310.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.310.0.tgz#d8fb1223fee7e289a81e28177fe55dedf4d2745e"
@@ -177,6 +321,39 @@
"@aws-sdk/types" "3.310.0"
tslib "^2.5.0"
"@aws-sdk/credential-provider-ini@3.388.0":
version "3.388.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.388.0.tgz#284b6dd2da4f3f8f53b2fa1838085148a478b936"
integrity sha512-3dg3A8AiZ5vXkSAYyyI3V/AW3Eo6KQJyE/glA+Nr2M0oAjT4z3vHhS3pf2B+hfKGZBTuKKgxusrrhrQABd/Diw==
dependencies:
"@aws-sdk/credential-provider-env" "3.387.0"
"@aws-sdk/credential-provider-process" "3.387.0"
"@aws-sdk/credential-provider-sso" "3.388.0"
"@aws-sdk/credential-provider-web-identity" "3.387.0"
"@aws-sdk/types" "3.387.0"
"@smithy/credential-provider-imds" "^2.0.0"
"@smithy/property-provider" "^2.0.0"
"@smithy/shared-ini-file-loader" "^2.0.0"
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@aws-sdk/credential-provider-node@3.388.0":
version "3.388.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.388.0.tgz#4c1599e2fdd94cff61f1d5568cade8e595cf4da2"
integrity sha512-BqWAkIG08gj/wevpesaZhAjALjfUNVjseHQRk+DNUoHIfyibW7Ahf3q/GIPs11dA2o8ECwR9/fo68Sq+sK799A==
dependencies:
"@aws-sdk/credential-provider-env" "3.387.0"
"@aws-sdk/credential-provider-ini" "3.388.0"
"@aws-sdk/credential-provider-process" "3.387.0"
"@aws-sdk/credential-provider-sso" "3.388.0"
"@aws-sdk/credential-provider-web-identity" "3.387.0"
"@aws-sdk/types" "3.387.0"
"@smithy/credential-provider-imds" "^2.0.0"
"@smithy/property-provider" "^2.0.0"
"@smithy/shared-ini-file-loader" "^2.0.0"
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@aws-sdk/credential-provider-node@^3.319.0":
version "3.319.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.319.0.tgz#51c8cd9d676d5b3ef80e88282fc1925946b1aaaf"
@@ -203,6 +380,17 @@
"@aws-sdk/types" "3.310.0"
tslib "^2.5.0"
"@aws-sdk/credential-provider-process@3.387.0":
version "3.387.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.387.0.tgz#114acfbcf9bd289e549fb3fd48acc1a71d7c75b7"
integrity sha512-tQScLHmDlqkQN+mqw4s3cxepEUeHYDhFl5eH+J8puvPqWjXMYpCEdY79SAtWs6SZd4CWiZ0VLeYU6xQBZengbQ==
dependencies:
"@aws-sdk/types" "3.387.0"
"@smithy/property-provider" "^2.0.0"
"@smithy/shared-ini-file-loader" "^2.0.0"
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@aws-sdk/credential-provider-sso@3.319.0":
version "3.319.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.319.0.tgz#c7bbea82e28bfbbafdb7d729239464c7ae38f7d0"
@@ -215,6 +403,19 @@
"@aws-sdk/types" "3.310.0"
tslib "^2.5.0"
"@aws-sdk/credential-provider-sso@3.388.0":
version "3.388.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.388.0.tgz#39868ebd160d24348287c8a8e57908f6a5d86046"
integrity sha512-RH02+rntaO0UhnSBr42n+7q8HOztc+Dets/hh6cWovf3Yi9s9ghLgYLN9FXpSosfot3XkmT/HOCa+CphAmGN9A==
dependencies:
"@aws-sdk/client-sso" "3.387.0"
"@aws-sdk/token-providers" "3.388.0"
"@aws-sdk/types" "3.387.0"
"@smithy/property-provider" "^2.0.0"
"@smithy/shared-ini-file-loader" "^2.0.0"
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@aws-sdk/credential-provider-web-identity@3.310.0":
version "3.310.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.310.0.tgz#c9fa09b0068027e58d31178e3fa06bf4e9ae9d36"
@@ -224,6 +425,16 @@
"@aws-sdk/types" "3.310.0"
tslib "^2.5.0"
"@aws-sdk/credential-provider-web-identity@3.387.0":
version "3.387.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.387.0.tgz#f15431ce00dbfe4f937b4afc706254759a096396"
integrity sha512-6ueMPl+J3KWv6ZaAWF4Z138QCuBVFZRVAgwbtP3BNqWrrs4Q6TPksOQJ79lRDMpv0EUoyVl04B6lldNlhN8RdA==
dependencies:
"@aws-sdk/types" "3.387.0"
"@smithy/property-provider" "^2.0.0"
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@aws-sdk/fetch-http-handler@3.310.0":
version "3.310.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.310.0.tgz#f31006b7b3103683d72e177cd27d80354f7a37c4"
@@ -289,6 +500,16 @@
"@aws-sdk/types" "3.310.0"
tslib "^2.5.0"
"@aws-sdk/middleware-host-header@3.387.0":
version "3.387.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.387.0.tgz#17c4948b83bb42ed04bdc2346fce4e4f980691e5"
integrity sha512-EWm9PXSr8dSp7hnRth1U7OfelXQp9dLf1yS1kUL+UhppYDJpjhdP7ql3NI4xJKw8e76sP2FuJYEuzWnJHuWoyQ==
dependencies:
"@aws-sdk/types" "3.387.0"
"@smithy/protocol-http" "^2.0.2"
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@aws-sdk/middleware-logger@3.310.0":
version "3.310.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.310.0.tgz#8cc6381f49ef867cae1364b8517f939629e4dd9d"
@@ -297,6 +518,15 @@
"@aws-sdk/types" "3.310.0"
tslib "^2.5.0"
"@aws-sdk/middleware-logger@3.387.0":
version "3.387.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.387.0.tgz#bbc05eb087989d6addecc58f1baeb39334851e6e"
integrity sha512-FjAvJr1XyaInT81RxUwgifnbXoFJrRBFc64XeFJgFanGIQCWLYxRrK2HV9eBpao/AycbmuoHgLd/f0sa4hZFoQ==
dependencies:
"@aws-sdk/types" "3.387.0"
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@aws-sdk/middleware-recursion-detection@3.310.0":
version "3.310.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.310.0.tgz#020c986ed8da751bd613fd84c8c8a805c89e0952"
@@ -306,6 +536,16 @@
"@aws-sdk/types" "3.310.0"
tslib "^2.5.0"
"@aws-sdk/middleware-recursion-detection@3.387.0":
version "3.387.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.387.0.tgz#34beba7dc436dcf13065f5ad99cc239f2f6175b9"
integrity sha512-ZF45T785ru8OwvYZw6awD9Z76OwSMM1eZzj2eY+FDz1cHfkpLjxEiti2iIH1FxbyK7n9ZqDUx29lVlCv238YyQ==
dependencies:
"@aws-sdk/types" "3.387.0"
"@smithy/protocol-http" "^2.0.2"
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@aws-sdk/middleware-retry@3.310.0":
version "3.310.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-retry/-/middleware-retry-3.310.0.tgz#12e95e962875d44af4acbdebe02db337a1ad5c35"
@@ -319,6 +559,16 @@
tslib "^2.5.0"
uuid "^8.3.2"
"@aws-sdk/middleware-sdk-sts@3.387.0":
version "3.387.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.387.0.tgz#6bd1e4eb17acc7387fa4231da52378ef77e10b1b"
integrity sha512-7ZzRKOJ4V/JDQmKz9z+FjZqw59mrMATEMLR6ff0H0JHMX0Uk5IX8TQB058ss+ar14qeJ4UcteYzCqHNI0O1BHw==
dependencies:
"@aws-sdk/middleware-signing" "3.387.0"
"@aws-sdk/types" "3.387.0"
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@aws-sdk/middleware-serde@3.310.0":
version "3.310.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-serde/-/middleware-serde-3.310.0.tgz#e334031b66a1a155375ec901478b26570fbe1783"
@@ -327,6 +577,19 @@
"@aws-sdk/types" "3.310.0"
tslib "^2.5.0"
"@aws-sdk/middleware-signing@3.387.0":
version "3.387.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-signing/-/middleware-signing-3.387.0.tgz#74bf5a9cf35239b5745a384a9d8f6f92afbd8328"
integrity sha512-oJXlE0MES8gxNLo137PPNNiOICQGOaETTvq3kBSJgb/gtEAxQajMIlaNT7s1wsjOAruFHt4975nCXuY4lpx7GQ==
dependencies:
"@aws-sdk/types" "3.387.0"
"@smithy/property-provider" "^2.0.0"
"@smithy/protocol-http" "^2.0.2"
"@smithy/signature-v4" "^2.0.0"
"@smithy/types" "^2.1.0"
"@smithy/util-middleware" "^2.0.0"
tslib "^2.5.0"
"@aws-sdk/middleware-stack@3.310.0":
version "3.310.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-stack/-/middleware-stack-3.310.0.tgz#06c83963998fbdc83e99b67a7a138529312a6224"
@@ -344,6 +607,17 @@
"@aws-sdk/util-endpoints" "3.319.0"
tslib "^2.5.0"
"@aws-sdk/middleware-user-agent@3.387.0":
version "3.387.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.387.0.tgz#aa5f9eb4f3cb4d6e0df879d8d84ccaf4f8baf8e5"
integrity sha512-hTfFTwDtp86xS98BKa+RFuLfcvGftxwzrbZeisZV8hdb4ZhvNXjSxnvM3vetW0GUEnY9xHPSGyp2ERRTinPKFQ==
dependencies:
"@aws-sdk/types" "3.387.0"
"@aws-sdk/util-endpoints" "3.387.0"
"@smithy/protocol-http" "^2.0.2"
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@aws-sdk/node-config-provider@3.310.0":
version "3.310.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/node-config-provider/-/node-config-provider-3.310.0.tgz#ba8fb41af2db0316291ba9002267627553ec65ac"
@@ -431,6 +705,47 @@
"@aws-sdk/types" "3.310.0"
tslib "^2.5.0"
"@aws-sdk/token-providers@3.388.0":
version "3.388.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.388.0.tgz#50000f5ca32b58614542a6e25918bc32585535cb"
integrity sha512-2lo1gFJl624kfjo/YdU6zW+k6dEwhoqjNkDNbOZEFgS1KDofHe9GX8W4/ReKb0Ggho5/EcjzZ53/1CjkzUq4tA==
dependencies:
"@aws-crypto/sha256-browser" "3.0.0"
"@aws-crypto/sha256-js" "3.0.0"
"@aws-sdk/middleware-host-header" "3.387.0"
"@aws-sdk/middleware-logger" "3.387.0"
"@aws-sdk/middleware-recursion-detection" "3.387.0"
"@aws-sdk/middleware-user-agent" "3.387.0"
"@aws-sdk/types" "3.387.0"
"@aws-sdk/util-endpoints" "3.387.0"
"@aws-sdk/util-user-agent-browser" "3.387.0"
"@aws-sdk/util-user-agent-node" "3.387.0"
"@smithy/config-resolver" "^2.0.2"
"@smithy/fetch-http-handler" "^2.0.2"
"@smithy/hash-node" "^2.0.2"
"@smithy/invalid-dependency" "^2.0.2"
"@smithy/middleware-content-length" "^2.0.2"
"@smithy/middleware-endpoint" "^2.0.2"
"@smithy/middleware-retry" "^2.0.2"
"@smithy/middleware-serde" "^2.0.2"
"@smithy/middleware-stack" "^2.0.0"
"@smithy/node-config-provider" "^2.0.2"
"@smithy/node-http-handler" "^2.0.2"
"@smithy/property-provider" "^2.0.0"
"@smithy/protocol-http" "^2.0.2"
"@smithy/shared-ini-file-loader" "^2.0.0"
"@smithy/smithy-client" "^2.0.2"
"@smithy/types" "^2.1.0"
"@smithy/url-parser" "^2.0.2"
"@smithy/util-base64" "^2.0.0"
"@smithy/util-body-length-browser" "^2.0.0"
"@smithy/util-body-length-node" "^2.0.0"
"@smithy/util-defaults-mode-browser" "^2.0.2"
"@smithy/util-defaults-mode-node" "^2.0.2"
"@smithy/util-retry" "^2.0.0"
"@smithy/util-utf8" "^2.0.0"
tslib "^2.5.0"
"@aws-sdk/types@3.310.0", "@aws-sdk/types@^3.222.0":
version "3.310.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.310.0.tgz#b83a0580feb38b58417abb8b4ed3eae1a0cb7bc1"
@@ -438,6 +753,14 @@
dependencies:
tslib "^2.5.0"
"@aws-sdk/types@3.387.0":
version "3.387.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.387.0.tgz#15a968344956b2587dbab1224718d72329e050f4"
integrity sha512-YTjFabNwjTF+6yl88f0/tWff018qmmgMmjlw45s6sdVKueWxdxV68U7gepNLF2nhaQPZa6FDOBoA51NaviVs0Q==
dependencies:
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@aws-sdk/url-parser@3.310.0":
version "3.310.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/url-parser/-/url-parser-3.310.0.tgz#928c9eac2e3d74c3c5db4c6e364a1de00185dcaa"
@@ -514,6 +837,14 @@
"@aws-sdk/types" "3.310.0"
tslib "^2.5.0"
"@aws-sdk/util-endpoints@3.387.0":
version "3.387.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.387.0.tgz#86d7611527ce916c39dfc02641b8be6e0ad8f1f4"
integrity sha512-g7kvuCXehGXHHBw9PkSQdwVyDFmNUZLmfrRmqMyrMDG9QLQrxr4pyWcSaYgTE16yUzhQQOR+QSey+BL6W9/N6g==
dependencies:
"@aws-sdk/types" "3.387.0"
tslib "^2.5.0"
"@aws-sdk/util-locate-window@^3.0.0":
version "3.310.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/util-locate-window/-/util-locate-window-3.310.0.tgz#b071baf050301adee89051032bd4139bba32cc40"
@@ -552,6 +883,16 @@
bowser "^2.11.0"
tslib "^2.5.0"
"@aws-sdk/util-user-agent-browser@3.387.0":
version "3.387.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.387.0.tgz#a59409a168a73a3ce08c0ac831593f864490078e"
integrity sha512-lpgSVvDqx+JjHZCTYs/yQSS7J71dPlJeAlvxc7bmx5m+vfwKe07HAnIs+929DngS0QbAp/VaXbTiMFsInLkO4Q==
dependencies:
"@aws-sdk/types" "3.387.0"
"@smithy/types" "^2.1.0"
bowser "^2.11.0"
tslib "^2.5.0"
"@aws-sdk/util-user-agent-node@3.310.0":
version "3.310.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.310.0.tgz#ebefbedc5a4759adc958885741628ec0de1ab197"
@@ -561,6 +902,16 @@
"@aws-sdk/types" "3.310.0"
tslib "^2.5.0"
"@aws-sdk/util-user-agent-node@3.387.0":
version "3.387.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.387.0.tgz#54ae2e17fb3738c018891bdb67ab4e4cce219e6f"
integrity sha512-r9OVkcWpRYatjLhJacuHFgvO2T5s/Nu5DDbScMrkUD8b4aGIIqsrdZji0vZy9FCjsUFQMM92t9nt4SejrGjChA==
dependencies:
"@aws-sdk/types" "3.387.0"
"@smithy/node-config-provider" "^2.0.2"
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@aws-sdk/util-utf8-browser@^3.0.0":
version "3.259.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz#3275a6f5eb334f96ca76635b961d3c50259fd9ff"
@@ -841,6 +1192,346 @@
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
"@smithy/abort-controller@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/abort-controller/-/abort-controller-2.0.2.tgz#e2188247a1723b58d60b0803f3ba24b76a714413"
integrity sha512-ln5Cob0mksym62sLr7NiPOSqJ0jKao4qjfcNLDdgINM1lQI12hXrZBlKdPHbXJqpKhKiECDgonMoqCM8bigq4g==
dependencies:
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@smithy/config-resolver@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/config-resolver/-/config-resolver-2.0.2.tgz#64496d2f2f9f1482d2e982d3dc057dccc4ba97db"
integrity sha512-0kdsqBL6BdmSbdU6YaDkodVBMua5MuQQluC3nocJ7OJ6PnOuM7i2FEQHE46LBadLqT+CimlDSM+6j91uHNL1ng==
dependencies:
"@smithy/types" "^2.1.0"
"@smithy/util-config-provider" "^2.0.0"
"@smithy/util-middleware" "^2.0.0"
tslib "^2.5.0"
"@smithy/credential-provider-imds@^2.0.0", "@smithy/credential-provider-imds@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/credential-provider-imds/-/credential-provider-imds-2.0.2.tgz#9096ff1a2ceb235497a62d469ac70086b96022ad"
integrity sha512-mbWFYEZ00LBRDk3WvcXViwpdpkJQcfrM3seuKzFxZnF6wIBLMwrcWcsj+OUC/1L+86m8aQY9imXMAaQsAoGxow==
dependencies:
"@smithy/node-config-provider" "^2.0.2"
"@smithy/property-provider" "^2.0.2"
"@smithy/types" "^2.1.0"
"@smithy/url-parser" "^2.0.2"
tslib "^2.5.0"
"@smithy/eventstream-codec@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/eventstream-codec/-/eventstream-codec-2.0.2.tgz#9d81c8d081ac28ba098d98b06cbb39955af1e09b"
integrity sha512-PQZiKx7fMnNwx4zxcUCm82VjnqK6wV4MEHSmMy3taj5dKfXV782IjRGyaDT+8TsmNqVdZIkve5zLRAzh+7kOhA==
dependencies:
"@aws-crypto/crc32" "3.0.0"
"@smithy/types" "^2.1.0"
"@smithy/util-hex-encoding" "^2.0.0"
tslib "^2.5.0"
"@smithy/fetch-http-handler@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/fetch-http-handler/-/fetch-http-handler-2.0.2.tgz#dcc0e9d365efd8feef4a54dd96a264735a1446b7"
integrity sha512-Wo2m1RaiXNSLF4J3D62LpdSoj/YYb+6tn0H8is1tSrzr7eXAdiYVBc0wIa23N0wT4zmN0iG/yNY6gTCDQ6799A==
dependencies:
"@smithy/protocol-http" "^2.0.2"
"@smithy/querystring-builder" "^2.0.2"
"@smithy/types" "^2.1.0"
"@smithy/util-base64" "^2.0.0"
tslib "^2.5.0"
"@smithy/hash-node@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/hash-node/-/hash-node-2.0.2.tgz#e968a3e7ab7072bd12297063e3770ae6d9249dee"
integrity sha512-JKDzZ1YVR7JzOBaJoWy3ToJCE86OQE6D4kOBvvVsu93a3lcF9kv6KYTKBYEWAjwOn/CpK4NH7mKB01OQ8H+aiA==
dependencies:
"@smithy/types" "^2.1.0"
"@smithy/util-buffer-from" "^2.0.0"
"@smithy/util-utf8" "^2.0.0"
tslib "^2.5.0"
"@smithy/invalid-dependency@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/invalid-dependency/-/invalid-dependency-2.0.2.tgz#1f7b6a860395e9f11fcdbdf3ac22fb95ce863c69"
integrity sha512-inQZQ5gCO3WRWuXpsc1YJ4KBjsvj2qsoU32yTIKznBWTCQe/D5Dp+sSaysqBqxe0VTZ+8nFEHdUMWUX2BxQThw==
dependencies:
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@smithy/is-array-buffer@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-2.0.0.tgz#8fa9b8040651e7ba0b2f6106e636a91354ff7d34"
integrity sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==
dependencies:
tslib "^2.5.0"
"@smithy/middleware-content-length@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/middleware-content-length/-/middleware-content-length-2.0.2.tgz#6167e8ca52cb5f2b06d3c76fa445080c45baaf25"
integrity sha512-FmHlNfuvYgDZE3fIx0G3rD/wLXfAmBYE4mVc/w6d7RllA7TygPzq2pfHL1iCMzWkWTdoAVnt3h4aavAZnhaxEQ==
dependencies:
"@smithy/protocol-http" "^2.0.2"
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@smithy/middleware-endpoint@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-2.0.2.tgz#29f4c8ae799ffb0891f96148eb754f8d0a41a97c"
integrity sha512-ropE7/c+g22QeluZ+By/B/WvVep0UFreX+IeRMGIO7EbOUPgqtJRXpbJFdG6JKB1uC+CdaJLn4MnZnVBpcyjuA==
dependencies:
"@smithy/middleware-serde" "^2.0.2"
"@smithy/types" "^2.1.0"
"@smithy/url-parser" "^2.0.2"
"@smithy/util-middleware" "^2.0.0"
tslib "^2.5.0"
"@smithy/middleware-retry@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-2.0.2.tgz#0d6feb551a5d546c720106435d2a4e7878fd8ea2"
integrity sha512-wtBUXqtZVriiXppYaFkUrybAPhFVX7vebnW/yVPliLMWMcguOMS58qhOYPZe3t9Wki2+mASfyu+kO3An8lAg2A==
dependencies:
"@smithy/protocol-http" "^2.0.2"
"@smithy/service-error-classification" "^2.0.0"
"@smithy/types" "^2.1.0"
"@smithy/util-middleware" "^2.0.0"
"@smithy/util-retry" "^2.0.0"
tslib "^2.5.0"
uuid "^8.3.2"
"@smithy/middleware-serde@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/middleware-serde/-/middleware-serde-2.0.2.tgz#a59f74e981be8b76ef18e272d525e24e3974dc82"
integrity sha512-Kw9xLdlueIaivUWslKB67WZ/cCUg3QnzYVIA3t5KfgsseEEuU4UxXw8NSTvIt71gqQloY+Um8ugS+idgxrWWnw==
dependencies:
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@smithy/middleware-stack@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@smithy/middleware-stack/-/middleware-stack-2.0.0.tgz#cd9f442c2788b1ef0ea6b32236d80c76b3c342e9"
integrity sha512-31XC1xNF65nlbc16yuh3wwTudmqs6qy4EseQUGF8A/p2m/5wdd/cnXJqpniy/XvXVwkHPz/GwV36HqzHtIKATQ==
dependencies:
tslib "^2.5.0"
"@smithy/node-config-provider@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/node-config-provider/-/node-config-provider-2.0.2.tgz#a15f125f7011ff82610297d899826b7ef7889867"
integrity sha512-9wVJccASfuCctNWrzR0zrDkf0ox3HCHGEhFlWL2LBoghUYuK28pVRBbG69wvnkhlHnB8dDZHagxH+Nq9dm7eWw==
dependencies:
"@smithy/property-provider" "^2.0.2"
"@smithy/shared-ini-file-loader" "^2.0.2"
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@smithy/node-http-handler@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-2.0.2.tgz#3c4d43352f5412cdb23ca075327ac997f5b03df2"
integrity sha512-lpZjmtmyZqSAtMPsbrLhb7XoAQ2kAHeuLY/csW6I2k+QyFvOk7cZeQsqEngWmZ9SJaeYiDCBINxAIM61i5WGLw==
dependencies:
"@smithy/abort-controller" "^2.0.2"
"@smithy/protocol-http" "^2.0.2"
"@smithy/querystring-builder" "^2.0.2"
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@smithy/property-provider@^2.0.0", "@smithy/property-provider@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/property-provider/-/property-provider-2.0.2.tgz#abe091d1e7dc5b617e3418b63eaed11363c96f21"
integrity sha512-DfaZ8cO+d/mgnMzIllcXcU4OYP+omiOl2LYdn/fTGpw/EAQSVzscYV2muV3sDDnuPYQ/r014hUqIxnF+pzh+SQ==
dependencies:
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@smithy/protocol-http@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-2.0.2.tgz#ec3d45a650cb5554b6aba1c38768f51fc9cf79b5"
integrity sha512-qWu8g1FUy+m36KpO1sREJSF7BaLmjw9AqOuwxLVVSdYz+nUQjc9tFAZ9LB6jJXKdsZFSjfkjHJBbhD78QdE7Rw==
dependencies:
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@smithy/querystring-builder@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-2.0.2.tgz#67a1bb503037c4666b5df56ad4b9e10bc525f568"
integrity sha512-H99LOMWEssfwqkOoTs4Y12UiZ7CTGQSX5Nrx5UkYgRbUEpC1GnnaprHiYrqclC58/xr4K76aNchdPyioxewMzA==
dependencies:
"@smithy/types" "^2.1.0"
"@smithy/util-uri-escape" "^2.0.0"
tslib "^2.5.0"
"@smithy/querystring-parser@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/querystring-parser/-/querystring-parser-2.0.2.tgz#d6b2562e7ae29282b144939e5fd439b17bdf61dd"
integrity sha512-L4VtKQ8O4/aWPQJbiFymbhAmxdfLnEaROh/Vs0OstJ7jtOZeBl2QJmuWY2V7hjt64W7V+tEn2sv6vVvnxkm/xQ==
dependencies:
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@smithy/service-error-classification@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@smithy/service-error-classification/-/service-error-classification-2.0.0.tgz#bbce07c9c529d9333d40db881fd4a1795dd84892"
integrity sha512-2z5Nafy1O0cTf69wKyNjGW/sNVMiqDnb4jgwfMG8ye8KnFJ5qmJpDccwIbJNhXIfbsxTg9SEec2oe1cexhMJvw==
"@smithy/shared-ini-file-loader@^2.0.0", "@smithy/shared-ini-file-loader@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.0.2.tgz#49b9bf384ece821352f50c8f6cb989edc77d2dbf"
integrity sha512-2VkNOM/82u4vatVdK5nfusgGIlvR48Fkq6me17Oc+V1iyxfR/1x0pG6LzW0br1qlGtzBYFZKmDyviBRcPVFTVw==
dependencies:
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@smithy/signature-v4@^2.0.0":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-2.0.2.tgz#c1ec6d9485a72039060e9a8fe2c02e0afb9d7764"
integrity sha512-YMooDEw/UmGxcXY4qWnSXkbPFsRloluSvyXVT678YPDN/K2AS1GzKfRsvSU7fbccOB4WF8MHZf2UqcRGEltE3Q==
dependencies:
"@smithy/eventstream-codec" "^2.0.2"
"@smithy/is-array-buffer" "^2.0.0"
"@smithy/types" "^2.1.0"
"@smithy/util-hex-encoding" "^2.0.0"
"@smithy/util-middleware" "^2.0.0"
"@smithy/util-uri-escape" "^2.0.0"
"@smithy/util-utf8" "^2.0.0"
tslib "^2.5.0"
"@smithy/smithy-client@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-2.0.2.tgz#3364bfb4afa73d57712b95cb9319f7c8324a104e"
integrity sha512-mDfokI8WwLU5C0gcQ4ww/zJI/WLGSh2+vdIA42JRnjfYUjJNH/rKfX9YOnn2eBOxl3loATERVUqkHmKe+P8s2Q==
dependencies:
"@smithy/middleware-stack" "^2.0.0"
"@smithy/types" "^2.1.0"
"@smithy/util-stream" "^2.0.2"
tslib "^2.5.0"
"@smithy/types@^2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@smithy/types/-/types-2.1.0.tgz#67fd47c25bbb0fd818951891bf7bcf19a8ee2fe6"
integrity sha512-KLsCsqxX0j2l99iP8s0f7LBlcsp7a7ceXGn0LPYPyVOsqmIKvSaPQajq0YevlL4T9Bm+DtcyXfBTbtBcLX1I7A==
dependencies:
tslib "^2.5.0"
"@smithy/url-parser@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/url-parser/-/url-parser-2.0.2.tgz#af50bd62298b209b1a16c80912a03460b7cb8994"
integrity sha512-X1mHCzrSVDlhVy7d3S7Vq+dTfYzwh4n7xGHhyJumu77nJqIss0lazVug85Pwo0DKIoO314wAOvMnBxNYDa+7wA==
dependencies:
"@smithy/querystring-parser" "^2.0.2"
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@smithy/util-base64@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@smithy/util-base64/-/util-base64-2.0.0.tgz#1beeabfb155471d1d41c8d0603be1351f883c444"
integrity sha512-Zb1E4xx+m5Lud8bbeYi5FkcMJMnn+1WUnJF3qD7rAdXpaL7UjkFQLdmW5fHadoKbdHpwH9vSR8EyTJFHJs++tA==
dependencies:
"@smithy/util-buffer-from" "^2.0.0"
tslib "^2.5.0"
"@smithy/util-body-length-browser@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@smithy/util-body-length-browser/-/util-body-length-browser-2.0.0.tgz#5447853003b4c73da3bc5f3c5e82c21d592d1650"
integrity sha512-JdDuS4ircJt+FDnaQj88TzZY3+njZ6O+D3uakS32f2VNnDo3vyEuNdBOh/oFd8Df1zSZOuH1HEChk2AOYDezZg==
dependencies:
tslib "^2.5.0"
"@smithy/util-body-length-node@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@smithy/util-body-length-node/-/util-body-length-node-2.0.0.tgz#4870b71cb9ded0123d984898ce952ce56896bc53"
integrity sha512-ZV7Z/WHTMxHJe/xL/56qZwSUcl63/5aaPAGjkfynJm4poILjdD4GmFI+V+YWabh2WJIjwTKZ5PNsuvPQKt93Mg==
dependencies:
tslib "^2.5.0"
"@smithy/util-buffer-from@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-2.0.0.tgz#7eb75d72288b6b3001bc5f75b48b711513091deb"
integrity sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==
dependencies:
"@smithy/is-array-buffer" "^2.0.0"
tslib "^2.5.0"
"@smithy/util-config-provider@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@smithy/util-config-provider/-/util-config-provider-2.0.0.tgz#4dd6a793605559d94267312fd06d0f58784b4c38"
integrity sha512-xCQ6UapcIWKxXHEU4Mcs2s7LcFQRiU3XEluM2WcCjjBtQkUN71Tb+ydGmJFPxMUrW/GWMgQEEGipLym4XG0jZg==
dependencies:
tslib "^2.5.0"
"@smithy/util-defaults-mode-browser@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.0.2.tgz#fb3ad350573ddea0ff7222adc98e9ecc4155b0d3"
integrity sha512-c2tMMjb624XLuzmlRoZpnFOkejVxcgw3WQKdmgdGZYZapcLzXyC0H9JhnXMjQCt30GqLTlsILRNVBYwFRbw/4Q==
dependencies:
"@smithy/property-provider" "^2.0.2"
"@smithy/types" "^2.1.0"
bowser "^2.11.0"
tslib "^2.5.0"
"@smithy/util-defaults-mode-node@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.0.2.tgz#2e16e3eb57427c76604c255c38d9e1eacd385d7e"
integrity sha512-gt7m5LLqUtEKldJLyc14DE4kb85vxwomvt9AfEMEvWM4VwfWS1kGJqiStZFb5KNqnQPXw8vvpgLTi8NrWAOXqg==
dependencies:
"@smithy/config-resolver" "^2.0.2"
"@smithy/credential-provider-imds" "^2.0.2"
"@smithy/node-config-provider" "^2.0.2"
"@smithy/property-provider" "^2.0.2"
"@smithy/types" "^2.1.0"
tslib "^2.5.0"
"@smithy/util-hex-encoding@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@smithy/util-hex-encoding/-/util-hex-encoding-2.0.0.tgz#0aa3515acd2b005c6d55675e377080a7c513b59e"
integrity sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA==
dependencies:
tslib "^2.5.0"
"@smithy/util-middleware@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@smithy/util-middleware/-/util-middleware-2.0.0.tgz#706681d4a1686544a2275f68266304233f372c99"
integrity sha512-eCWX4ECuDHn1wuyyDdGdUWnT4OGyIzV0LN1xRttBFMPI9Ff/4heSHVxneyiMtOB//zpXWCha1/SWHJOZstG7kA==
dependencies:
tslib "^2.5.0"
"@smithy/util-retry@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@smithy/util-retry/-/util-retry-2.0.0.tgz#7ac5d5f12383a9d9b2a43f9ff25f3866c8727c24"
integrity sha512-/dvJ8afrElasuiiIttRJeoS2sy8YXpksQwiM/TcepqdRVp7u4ejd9C4IQURHNjlfPUT7Y6lCDSa2zQJbdHhVTg==
dependencies:
"@smithy/service-error-classification" "^2.0.0"
tslib "^2.5.0"
"@smithy/util-stream@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-2.0.2.tgz#cb4f3c4eca4253f77a780fd861630ed02d67b220"
integrity sha512-Mg9IJcKIu4YKlbzvpp1KLvh4JZLdcPgpxk+LICuDwzZCfxe47R9enVK8dNEiuyiIGK2ExbfvzCVT8IBru62vZw==
dependencies:
"@smithy/fetch-http-handler" "^2.0.2"
"@smithy/node-http-handler" "^2.0.2"
"@smithy/types" "^2.1.0"
"@smithy/util-base64" "^2.0.0"
"@smithy/util-buffer-from" "^2.0.0"
"@smithy/util-hex-encoding" "^2.0.0"
"@smithy/util-utf8" "^2.0.0"
tslib "^2.5.0"
"@smithy/util-uri-escape@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@smithy/util-uri-escape/-/util-uri-escape-2.0.0.tgz#19955b1a0f517a87ae77ac729e0e411963dfda95"
integrity sha512-ebkxsqinSdEooQduuk9CbKcI+wheijxEb3utGXkCoYQkJnwTnLbH1JXGimJtUkQwNQbsbuYwG2+aFVyZf5TLaw==
dependencies:
tslib "^2.5.0"
"@smithy/util-utf8@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-2.0.0.tgz#b4da87566ea7757435e153799df9da717262ad42"
integrity sha512-rctU1VkziY84n5OXe3bPNpKR001ZCME2JCaBBFgtiM2hfKbHFudc/BkMuPab8hRbLd0j3vbnBTTZ1igBf0wgiQ==
dependencies:
"@smithy/util-buffer-from" "^2.0.0"
tslib "^2.5.0"
"@socket.io/component-emitter@~3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553"
@@ -2136,6 +2827,13 @@ fast-text-encoding@^1.0.0, fast-text-encoding@^1.0.3:
resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz#0aa25f7f638222e3396d72bf936afcf1d42d6867"
integrity sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==
fast-xml-parser@4.2.5:
version "4.2.5"
resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz#a6747a09296a6cb34f2ae634019bf1738f3b421f"
integrity sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==
dependencies:
strnum "^1.0.5"
faye-websocket@0.11.4:
version "0.11.4"
resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da"
@@ -4187,6 +4885,11 @@ stripe@^9.15.0:
"@types/node" ">=8.1.0"
qs "^6.10.3"
strnum@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db"
integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==
stubs@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/stubs/-/stubs-3.0.0.tgz#e8d2ba1fa9c90570303c030b6900f7d5f89abe5b"