Compare commits
95 Commits
feature/20
...
release/20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2b2a5399d | ||
|
|
ba5400d04a | ||
|
|
0190e737c1 | ||
|
|
8f52efbaca | ||
|
|
d6dc5db185 | ||
|
|
58d1859640 | ||
|
|
dba648aea2 | ||
|
|
12f6206f88 | ||
|
|
7d9fd06b6d | ||
|
|
b68de683b0 | ||
|
|
ef79ccc299 | ||
|
|
8ff2a6e6c4 | ||
|
|
be2cfb908a | ||
|
|
77819b06fe | ||
|
|
4e936b4cff | ||
|
|
f45f351678 | ||
|
|
311171fa0a | ||
|
|
7e05f03f4d | ||
|
|
18618efdc0 | ||
|
|
fe5f4f2727 | ||
|
|
fd7c907b8f | ||
|
|
e86160e530 | ||
|
|
9f7e0d611a | ||
|
|
0b523efa95 | ||
|
|
6b64499e24 | ||
|
|
e78e628bec | ||
|
|
ed8eb51c2f | ||
|
|
124d68ef68 | ||
|
|
3e802c1465 | ||
|
|
ac18e78897 | ||
|
|
e37ee39e88 | ||
|
|
fed16a4aa3 | ||
|
|
8c94dfce9e | ||
|
|
69c13dd052 | ||
|
|
a5fe54164e | ||
|
|
5ecd5a5a5c | ||
|
|
ed45347c23 | ||
|
|
3e0385479f | ||
|
|
36f833be91 | ||
|
|
8fb39f9ea4 | ||
|
|
5b84ebbc25 | ||
|
|
b0ec7867b5 | ||
|
|
4e4c59ce4d | ||
|
|
e9bf1c05ad | ||
|
|
7ac1fa5abf | ||
|
|
a489ac1d26 | ||
|
|
4d7a7442ce | ||
|
|
a19bce5a37 | ||
|
|
35782244bf | ||
|
|
7407429344 | ||
|
|
55c532f6e2 | ||
|
|
d3c8b5d731 | ||
|
|
cf09f98d7e | ||
|
|
d8e8a8e4e9 | ||
|
|
65210dea2f | ||
|
|
0c9b850872 | ||
|
|
99486830b7 | ||
|
|
74a62a46d3 | ||
|
|
d306041bcf | ||
|
|
ae8a924cd6 | ||
|
|
6ab1b9f787 | ||
|
|
46ddc440fe | ||
|
|
6bf8eacfbd | ||
|
|
b2fa4f220d | ||
|
|
2f175c304c | ||
|
|
79714e5708 | ||
|
|
7c5aa9c913 | ||
|
|
59b8bae182 | ||
|
|
35323ba624 | ||
|
|
8ca3741a52 | ||
|
|
c3c021774e | ||
|
|
6b811d635b | ||
|
|
97aecd3ddc | ||
|
|
c4fdef445e | ||
|
|
e642087360 | ||
|
|
6ce2d2723b | ||
|
|
79e11dda4c | ||
|
|
f75f88840f | ||
|
|
11cfef904b | ||
|
|
a45dcd307b | ||
|
|
7f4a36038e | ||
|
|
12307cbd56 | ||
|
|
c9ed8a9360 | ||
|
|
120d6f9f5f | ||
|
|
6f64cb71f2 | ||
|
|
7a6a834998 | ||
|
|
7999895323 | ||
|
|
71ef3dadc5 | ||
|
|
f0d6c5e1b1 | ||
|
|
991df9c48f | ||
|
|
84b39f3d2b | ||
|
|
4ab0947cc8 | ||
|
|
8cbef14ea3 | ||
|
|
105ecd4221 | ||
|
|
3176cfcc56 |
6
_reference/Responsibility Center Setup.md
Normal file
6
_reference/Responsibility Center Setup.md
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -4,39 +4,39 @@
|
||||
"private": true,
|
||||
"proxy": "http://localhost:5000",
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.3.17",
|
||||
"@craco/craco": "^5.9.0",
|
||||
"@fingerprintjs/fingerprintjs": "^3.1.2",
|
||||
"@apollo/client": "^3.3.21",
|
||||
"@craco/craco": "^6.2.0",
|
||||
"@fingerprintjs/fingerprintjs": "^3.2.0",
|
||||
"@lourenci/react-kanban": "^2.1.0",
|
||||
"@sentry/react": "^6.3.6",
|
||||
"@sentry/tracing": "^6.3.6",
|
||||
"@sentry/react": "^6.10.0",
|
||||
"@sentry/tracing": "^6.10.0",
|
||||
"@stripe/react-stripe-js": "^1.4.0",
|
||||
"@stripe/stripe-js": "^1.14.0",
|
||||
"@tanem/react-nprogress": "^3.0.65",
|
||||
"antd": "^4.15.5",
|
||||
"@stripe/stripe-js": "^1.16.0",
|
||||
"@tanem/react-nprogress": "^3.0.74",
|
||||
"antd": "^4.16.8",
|
||||
"apollo-link-logger": "^2.0.0",
|
||||
"axios": "^0.21.1",
|
||||
"craco-less": "^1.17.1",
|
||||
"dinero.js": "^1.8.1",
|
||||
"dotenv": "^9.0.2",
|
||||
"craco-less": "^1.18.0",
|
||||
"dinero.js": "^1.9.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"enquire-js": "^0.2.1",
|
||||
"env-cmd": "^10.1.0",
|
||||
"exifr": "^7.0.0",
|
||||
"firebase": "^8.6.0",
|
||||
"graphql": "^15.5.0",
|
||||
"i18next": "^20.2.2",
|
||||
"i18next-browser-languagedetector": "^6.1.1",
|
||||
"jsoneditor": "^9.4.1",
|
||||
"exifr": "^7.1.2",
|
||||
"firebase": "^8.7.1",
|
||||
"graphql": "^15.5.1",
|
||||
"i18next": "^20.3.4",
|
||||
"i18next-browser-languagedetector": "^6.1.2",
|
||||
"jsoneditor": "^9.5.2",
|
||||
"jsreport-browser-client-dist": "^1.3.0",
|
||||
"libphonenumber-js": "^1.9.17",
|
||||
"libphonenumber-js": "^1.9.22",
|
||||
"logrocket": "^1.2.0",
|
||||
"markerjs2": "^2.8.1",
|
||||
"markerjs2": "^2.9.0",
|
||||
"moment-business-days": "^1.2.0",
|
||||
"phone": "^2.4.21",
|
||||
"phone": "^3.1.2",
|
||||
"preval.macro": "^5.0.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"query-string": "^7.0.0",
|
||||
"rc-queue-anim": "^1.8.5",
|
||||
"query-string": "^7.0.1",
|
||||
"rc-queue-anim": "^2.0.0",
|
||||
"rc-scroll-anim": "^2.7.6",
|
||||
"react": "^17.0.1",
|
||||
"react-big-calendar": "^0.33.2",
|
||||
@@ -45,26 +45,26 @@
|
||||
"react-drag-listview": "^0.1.8",
|
||||
"react-grid-gallery": "^0.5.5",
|
||||
"react-grid-layout": "^1.2.5",
|
||||
"react-i18next": "^11.8.15",
|
||||
"react-i18next": "^11.11.3",
|
||||
"react-icons": "^4.2.0",
|
||||
"react-number-format": "^4.5.5",
|
||||
"react-number-format": "^4.6.4",
|
||||
"react-redux": "^7.2.4",
|
||||
"react-resizable": "^3.0.1",
|
||||
"react-resizable": "^3.0.4",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "^4.0.3",
|
||||
"react-sublime-video": "^0.2.5",
|
||||
"react-virtualized": "^9.22.3",
|
||||
"recharts": "^2.0.7",
|
||||
"recharts": "^2.0.10",
|
||||
"redux": "^4.1.0",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-saga": "^1.1.3",
|
||||
"redux-state-sync": "^3.1.2",
|
||||
"reselect": "^4.0.0",
|
||||
"sass": "^1.32.13",
|
||||
"socket.io-client": "^4.1.2",
|
||||
"sass": "^1.35.2",
|
||||
"socket.io-client": "^4.1.3",
|
||||
"styled-components": "^5.3.0",
|
||||
"subscriptions-transport-ws": "^0.9.18",
|
||||
"web-vitals": "^1.1.2",
|
||||
"web-vitals": "^2.1.0",
|
||||
"workbox-background-sync": "^6.1.5",
|
||||
"workbox-broadcast-update": "^6.1.5",
|
||||
"workbox-cacheable-response": "^6.1.5",
|
||||
|
||||
@@ -109,6 +109,17 @@ export default function AccountingPayablesTableComponent({ loading, bills }) {
|
||||
<Checkbox disabled checked={record.is_credit_memo} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("exportlogs.labels.attempts"),
|
||||
dataIndex: "attempts",
|
||||
key: "attempts",
|
||||
|
||||
render: (text, record) => {
|
||||
const success = record.exportlogs.filter((e) => e.successful).length;
|
||||
const attempts = record.exportlogs.length;
|
||||
return `${success}/${attempts}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("general.labels.actions"),
|
||||
dataIndex: "actions",
|
||||
@@ -121,6 +132,7 @@ export default function AccountingPayablesTableComponent({ loading, bills }) {
|
||||
billId={record.id}
|
||||
disabled={transInProgress || !!record.exported}
|
||||
loadingCallback={setTransInProgress}
|
||||
setSelectedBills={setSelectedBills}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
|
||||
@@ -108,7 +108,17 @@ export default function AccountingPayablesTableComponent({
|
||||
<DateTimeFormatter>{record.exportedat}</DateTimeFormatter>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("exportlogs.labels.attempts"),
|
||||
dataIndex: "attempts",
|
||||
key: "attempts",
|
||||
|
||||
render: (text, record) => {
|
||||
const success = record.exportlogs.filter((e) => e.successful).length;
|
||||
const attempts = record.exportlogs.length;
|
||||
return `${success}/${attempts}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("general.labels.actions"),
|
||||
dataIndex: "actions",
|
||||
@@ -120,6 +130,7 @@ export default function AccountingPayablesTableComponent({
|
||||
paymentId={record.id}
|
||||
disabled={transInProgress || !!record.exportedat}
|
||||
loadingCallback={setTransInProgress}
|
||||
setSelectedPayments={setSelectedPayments}
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -114,17 +114,28 @@ export default function AccountingReceivablesTableComponent({ loading, jobs }) {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("exportlogs.labels.attempts"),
|
||||
dataIndex: "attempts",
|
||||
key: "attempts",
|
||||
|
||||
render: (text, record) => {
|
||||
const success = record.exportlogs.filter((e) => e.successful).length;
|
||||
const attempts = record.exportlogs.length;
|
||||
return `${success}/${attempts}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("general.labels.actions"),
|
||||
dataIndex: "actions",
|
||||
key: "actions",
|
||||
sorter: (a, b) => a.clm_total - b.clm_total,
|
||||
|
||||
render: (text, record) => (
|
||||
<Space wrap>
|
||||
<JobExportButton
|
||||
jobId={record.id}
|
||||
disabled={!!record.date_exported}
|
||||
setSelectedJobs={setSelectedJobs}
|
||||
/>
|
||||
<Link to={`/manage/jobs/${record.id}/close`}>
|
||||
<Button>{t("jobs.labels.viewallocations")}</Button>
|
||||
|
||||
@@ -26,6 +26,8 @@ import BillReeportButtonComponent from "../bill-reexport-button/bill-reexport-bu
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -33,6 +35,8 @@ const mapStateToProps = createStructuredSelector({
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setPartsOrderContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
@@ -40,7 +44,10 @@ export default connect(
|
||||
mapDispatchToProps
|
||||
)(BillDetailEditcontainer);
|
||||
|
||||
export function BillDetailEditcontainer({ setPartsOrderContext }) {
|
||||
export function BillDetailEditcontainer({
|
||||
setPartsOrderContext,
|
||||
insertAuditTrail,
|
||||
}) {
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const history = useHistory();
|
||||
const { t } = useTranslation();
|
||||
@@ -134,6 +141,12 @@ export function BillDetailEditcontainer({ setPartsOrderContext }) {
|
||||
});
|
||||
await Promise.all(updates);
|
||||
|
||||
insertAuditTrail({
|
||||
jobid: bill.jobid,
|
||||
billid: search.billid,
|
||||
operation: AuditTrailMapping.billupdated(bill.invoice_number),
|
||||
});
|
||||
|
||||
await refetch();
|
||||
form.setFieldsValue(transformData(data));
|
||||
form.resetFields();
|
||||
|
||||
@@ -11,12 +11,14 @@ import {
|
||||
QUERY_JOB_LBR_ADJUSTMENTS,
|
||||
UPDATE_JOB,
|
||||
} from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectBillEnterModal } from "../../redux/modals/modals.selectors";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import BillFormContainer from "../bill-form/bill-form.container";
|
||||
import { handleUpload } from "../documents-upload/documents-upload.utility";
|
||||
|
||||
@@ -27,6 +29,8 @@ const mapStateToProps = createStructuredSelector({
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("billEnter")),
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
});
|
||||
|
||||
function BillEnterModalContainer({
|
||||
@@ -34,6 +38,7 @@ function BillEnterModalContainer({
|
||||
toggleModalVisible,
|
||||
bodyshop,
|
||||
currentUser,
|
||||
insertAuditTrail,
|
||||
}) {
|
||||
const [form] = Form.useForm();
|
||||
const { t } = useTranslation();
|
||||
@@ -83,7 +88,7 @@ function BillEnterModalContainer({
|
||||
},
|
||||
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"],
|
||||
});
|
||||
console.log("adjustmentsToInsert", adjustmentsToInsert);
|
||||
|
||||
const adjKeys = Object.keys(adjustmentsToInsert);
|
||||
if (adjKeys.length > 0) {
|
||||
//Query the adjustments, merge, and update them.
|
||||
@@ -116,7 +121,12 @@ function BillEnterModalContainer({
|
||||
message: JSON.stringify(jobUpdate.errors),
|
||||
}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
insertAuditTrail({
|
||||
jobid: values.jobid,
|
||||
operation: AuditTrailMapping.jobmodifylbradj(),
|
||||
});
|
||||
}
|
||||
|
||||
if (!!r1.errors) {
|
||||
@@ -172,6 +182,12 @@ function BillEnterModalContainer({
|
||||
});
|
||||
if (billEnterModal.actions.refetch) billEnterModal.actions.refetch();
|
||||
|
||||
insertAuditTrail({
|
||||
jobid: values.jobid,
|
||||
billid: billId,
|
||||
operation: AuditTrailMapping.billposted(remainingValues.invoice_number),
|
||||
});
|
||||
|
||||
if (enterAgain) {
|
||||
form.resetFields();
|
||||
form.setFieldsValue({ billlines: [] });
|
||||
|
||||
@@ -72,9 +72,11 @@ export function BillEnterModalLinesComponent({
|
||||
quantity: opt.part_qty || 1,
|
||||
actual_price: opt.cost,
|
||||
cost_center: opt.part_type
|
||||
? responsibilityCenters.defaults.costs[
|
||||
? responsibilityCenters.defaults &&
|
||||
(responsibilityCenters.defaults.costs[
|
||||
opt.part_type
|
||||
] || null
|
||||
] ||
|
||||
null)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import Dinero from "dinero.js";
|
||||
|
||||
export const CalculateBillTotal = (invoice) => {
|
||||
const {
|
||||
total,
|
||||
billlines,
|
||||
federal_tax_rate,
|
||||
local_tax_rate,
|
||||
state_tax_rate,
|
||||
} = invoice;
|
||||
const { total, billlines, federal_tax_rate, local_tax_rate, state_tax_rate } =
|
||||
invoice;
|
||||
|
||||
//TODO Determine why this recalculates so many times.
|
||||
let subtotal = Dinero({ amount: 0 });
|
||||
@@ -20,8 +15,7 @@ export const CalculateBillTotal = (invoice) => {
|
||||
billlines.forEach((i) => {
|
||||
if (!!i) {
|
||||
const itemTotal = Dinero({
|
||||
amount:
|
||||
Math.round(((i.actual_cost || 0) * 100 + Number.EPSILON) * 100) / 100,
|
||||
amount: Math.round((i.actual_cost || 0) * 100),
|
||||
}).multiply(i.quantity || 1);
|
||||
|
||||
subtotal = subtotal.add(itemTotal);
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
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";
|
||||
@@ -14,6 +15,7 @@ import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//jobRO: selectJobReadOnly,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
@@ -26,6 +28,7 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
});
|
||||
|
||||
export function BillsListTableComponent({
|
||||
bodyshop,
|
||||
job,
|
||||
billsQuery,
|
||||
handleOnRowClick,
|
||||
@@ -52,7 +55,9 @@ export function BillsListTableComponent({
|
||||
)}
|
||||
<BillDeleteButton bill={record} />
|
||||
<Button
|
||||
disabled={record.is_credit_memo}
|
||||
disabled={
|
||||
record.is_credit_memo || record.vendorid === bodyshop.inhousevendorid
|
||||
}
|
||||
onClick={() =>
|
||||
setPartsOrderContext({
|
||||
actions: {},
|
||||
|
||||
@@ -34,7 +34,9 @@ export default function DashboardMonthlyRevenueGraph({ data, ...cardProps }) {
|
||||
let dailySales;
|
||||
if (!!jobsByDate[val]) {
|
||||
dailySales = jobsByDate[val].reduce((dayAcc, dayVal) => {
|
||||
return dayAcc.add(Dinero(dayVal.job_totals.totals.subtotal));
|
||||
return dayAcc.add(
|
||||
Dinero((dayVal.job_totals && dayVal.job_totals.totals.subtotal) || 0)
|
||||
);
|
||||
}, Dinero());
|
||||
} else {
|
||||
dailySales = Dinero();
|
||||
|
||||
@@ -13,7 +13,14 @@ export default function DashboardProjectedMonthlySales({ data, ...cardProps }) {
|
||||
const dollars =
|
||||
data.projected_monthly_sales &&
|
||||
data.projected_monthly_sales.reduce(
|
||||
(acc, val) => acc.add(Dinero(val.job_totals.totals.subtotal)),
|
||||
(acc, val) =>
|
||||
acc.add(
|
||||
Dinero(
|
||||
val.job_totals &&
|
||||
val.job_totals.totals &&
|
||||
val.job_totals.totals.subtotal
|
||||
)
|
||||
),
|
||||
Dinero()
|
||||
);
|
||||
return (
|
||||
|
||||
@@ -14,7 +14,8 @@ export default function DashboardTotalProductionDollars({
|
||||
const dollars =
|
||||
data.production_jobs &&
|
||||
data.production_jobs.reduce(
|
||||
(acc, val) => acc.add(Dinero(val.job_totals.totals.subtotal)),
|
||||
(acc, val) =>
|
||||
acc.add(Dinero(val.job_totals && val.job_totals.totals.subtotal)),
|
||||
Dinero()
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Button, Table } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import Dinero from "dinero.js";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(DmsAllocationsSummary);
|
||||
|
||||
export function DmsAllocationsSummary({ socket, bodyshop, jobId }) {
|
||||
const { t } = useTranslation();
|
||||
const [allocationsSummary, setAllocationsSummary] = useState([]);
|
||||
const columns = [
|
||||
{
|
||||
title: t("jobs.fields.dms.center"),
|
||||
dataIndex: "center",
|
||||
key: "center",
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.sale"),
|
||||
dataIndex: "sale",
|
||||
key: "sale",
|
||||
render: (text, record) => Dinero(record.sale).toFormat(),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.cost"),
|
||||
dataIndex: "cost",
|
||||
key: "cost",
|
||||
render: (text, record) => Dinero(record.cost).toFormat(),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.sale_dms_acctnumber"),
|
||||
dataIndex: "sale_dms_acctnumber",
|
||||
key: "sale_dms_acctnumber",
|
||||
render: (text, record) =>
|
||||
record.profitCenter && record.profitCenter.dms_acctnumber,
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.cost_dms_acctnumber"),
|
||||
dataIndex: "cost_dms_acctnumber",
|
||||
key: "cost_dms_acctnumber",
|
||||
render: (text, record) =>
|
||||
record.costCenter && record.costCenter.dms_acctnumber,
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.dms_wip_acctnumber"),
|
||||
dataIndex: "dms_wip_acctnumber",
|
||||
key: "dms_wip_acctnumber",
|
||||
render: (text, record) =>
|
||||
record.costCenter && record.costCenter.dms_wip_acctnumber,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Table
|
||||
title={() => (
|
||||
<Button
|
||||
onClick={() => {
|
||||
socket.emit("cdk-calculate-allocations", jobId, (ack) =>
|
||||
setAllocationsSummary(ack)
|
||||
);
|
||||
}}
|
||||
>
|
||||
Get
|
||||
</Button>
|
||||
)}
|
||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
||||
columns={columns}
|
||||
rowKey="center"
|
||||
dataSource={allocationsSummary}
|
||||
/>
|
||||
);
|
||||
}
|
||||
115
client/src/components/dms-cdk-makes/dms-cdk-makes.component.jsx
Normal file
115
client/src/components/dms-cdk-makes/dms-cdk-makes.component.jsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import React, { useState } from "react";
|
||||
import { Modal, Button, Table, Input } from "antd";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DmsCdkMakes);
|
||||
|
||||
export function DmsCdkMakes({ bodyshop, form, socket }) {
|
||||
const [makesList, setMakesList] = useState([]);
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [selectedModel, setSelectedModel] = useState(null);
|
||||
const { t } = useTranslation();
|
||||
const columns = [
|
||||
{
|
||||
title: t("jobs.fields.dms.makeFullName"),
|
||||
dataIndex: "makeFullName",
|
||||
key: "makeFullName",
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.modelFullName"),
|
||||
dataIndex: "modelFullName",
|
||||
key: "modelFullName",
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.makeCode"),
|
||||
dataIndex: "makeCode",
|
||||
key: "makeCode",
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.modelCode"),
|
||||
dataIndex: "modelCode",
|
||||
key: "modelCode",
|
||||
},
|
||||
];
|
||||
|
||||
const filteredMakes =
|
||||
searchText !== "" && searchText
|
||||
? makesList.filter(
|
||||
(make) =>
|
||||
searchText
|
||||
.split(" ")
|
||||
.some((v) =>
|
||||
make.makeFullName.toLowerCase().includes(v.toLowerCase())
|
||||
) ||
|
||||
searchText
|
||||
.split(" ")
|
||||
.some((v) =>
|
||||
make.modelFullName.toLowerCase().includes(v.toLowerCase())
|
||||
)
|
||||
)
|
||||
: makesList;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Modal width={"90%"} visible={visible} onCancel={() => setVisible(false)}>
|
||||
<Table
|
||||
title={() => (
|
||||
<Input.Search
|
||||
onSearch={(val) => setSearchText(val)}
|
||||
placeholder={t("general.labels.search")}
|
||||
/>
|
||||
)}
|
||||
columns={columns}
|
||||
loading={loading}
|
||||
id="id"
|
||||
dataSource={filteredMakes}
|
||||
onRow={(record) => {
|
||||
return {
|
||||
onClick: setSelectedModel(record),
|
||||
};
|
||||
}}
|
||||
rowSelection={{
|
||||
onSelect: (record, selected, ...props) => {
|
||||
console.log(
|
||||
"🚀 ~ file: dms-cdk-makes.component.jsx ~ line 85 ~ record, selected, ...props",
|
||||
record,
|
||||
selected,
|
||||
...props
|
||||
);
|
||||
|
||||
setSelectedModel(record);
|
||||
},
|
||||
|
||||
type: "radio",
|
||||
selectedRowKeys: [selectedModel && selectedModel.id],
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setVisible(true);
|
||||
setLoading(true);
|
||||
socket.emit("cdk-get-makes", bodyshop.cdk_dealerid, (makes) => {
|
||||
console.log("Called back", makes);
|
||||
setMakesList(makes);
|
||||
setLoading(false);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Get Makes
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { Button, Table } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { socket } from "../../pages/dms/dms.container";
|
||||
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
export default function DmsCustomerSelector() {
|
||||
const { t } = useTranslation();
|
||||
const [customerList, setcustomerList] = useState([]);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [selectedCustomer, setSelectedCustomer] = useState(null);
|
||||
|
||||
socket.on("cdk-select-customer", (customerList, callback) => {
|
||||
setVisible(true);
|
||||
setcustomerList(customerList);
|
||||
});
|
||||
|
||||
const onOk = () => {
|
||||
setVisible(false);
|
||||
socket.emit("cdk-selected-customer", selectedCustomer);
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("dms.fields.name1"),
|
||||
dataIndex: ["name1", "fullName"],
|
||||
key: "name1",
|
||||
sorter: (a, b) => alphaSort(a.name1?.fullName, b.name1?.fullName),
|
||||
},
|
||||
{
|
||||
title: t("dms.fields.name2"),
|
||||
dataIndex: ["name2", "fullName"],
|
||||
key: "name2",
|
||||
sorter: (a, b) => alphaSort(a.name2?.fullName, b.name2?.fullName),
|
||||
},
|
||||
{
|
||||
title: t("dms.fields.phone"),
|
||||
dataIndex: ["contactInfo", "mainTelephoneNumber", "value"],
|
||||
key: "phone",
|
||||
render: (record, value) => (
|
||||
<PhoneFormatter>
|
||||
{record.contactInfo?.mainTelephoneNumber?.value}
|
||||
</PhoneFormatter>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("dms.fields.address"),
|
||||
//dataIndex: ["name2", "fullName"],
|
||||
key: "address",
|
||||
render: (record, value) =>
|
||||
`${record.address?.addressLine[0]}, ${record.address?.city} ${record.address?.stateOrProvince} ${record.address?.postalCode}`,
|
||||
},
|
||||
];
|
||||
|
||||
if (!visible) return <></>;
|
||||
return (
|
||||
<Table
|
||||
title={() => (
|
||||
<div>
|
||||
<Button onClick={onOk}>Select</Button>
|
||||
</div>
|
||||
)}
|
||||
pagination={{ position: "top" }}
|
||||
columns={columns}
|
||||
rowKey={(record) => record.id.value}
|
||||
dataSource={customerList}
|
||||
//onChange={handleTableChange}
|
||||
rowSelection={{
|
||||
onSelect: (props) => {
|
||||
setSelectedCustomer(props.id.value);
|
||||
},
|
||||
type: "radio",
|
||||
selectedRowKeys: [selectedCustomer],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { Divider, Space, Tag, Timeline } from "antd";
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
setBreadcrumbs,
|
||||
setSelectedHeader,
|
||||
} from "../../redux/application/application.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DmsLogEvents);
|
||||
|
||||
export function DmsLogEvents({ socket, logs, bodyshop }) {
|
||||
return (
|
||||
<Timeline pending reverse={true}>
|
||||
{logs.map((log, idx) => (
|
||||
<Timeline.Item key={idx} color={LogLevelHierarchy(log.level)}>
|
||||
<Space wrap align="start" style={{}}>
|
||||
<Tag color={LogLevelHierarchy(log.level)}>{log.level}</Tag>
|
||||
<span>{moment(log.timestamp).format("MM/DD/YYYY HH:MM:ss")}</span>
|
||||
<Divider type="vertical" />
|
||||
<span>{log.message}</span>
|
||||
</Space>
|
||||
</Timeline.Item>
|
||||
))}
|
||||
</Timeline>
|
||||
);
|
||||
}
|
||||
|
||||
function LogLevelHierarchy(level) {
|
||||
switch (level) {
|
||||
case "TRACE":
|
||||
return "pink";
|
||||
case "DEBUG":
|
||||
return "orange";
|
||||
case "INFO":
|
||||
return "blue";
|
||||
case "WARNING":
|
||||
return "yellow";
|
||||
case "ERROR":
|
||||
return "red";
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
150
client/src/components/dms-post-form/dms-post-form.component.jsx
Normal file
150
client/src/components/dms-post-form/dms-post-form.component.jsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import { DeleteFilled } from "@ant-design/icons";
|
||||
import { Button, Form, Input } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DmsPostForm);
|
||||
|
||||
export function DmsPostForm({ bodyshop, socket, jobId }) {
|
||||
const [form] = Form.useForm();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Form form={form} layout="vertical">
|
||||
<LayoutFormRow>
|
||||
<Form.Item
|
||||
name="journal"
|
||||
label={t("jobs.fields.dms.journal")}
|
||||
initialValue={
|
||||
bodyshop.cdk_configuration &&
|
||||
bodyshop.cdk_configuration.default_journal
|
||||
}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="dms_make"
|
||||
label={t("jobs.fields.dms.dms_make")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="dms_make"
|
||||
label={t("jobs.fields.dms.dms_make")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<DmsCdkMakes form={form} socket={socket} />
|
||||
</LayoutFormRow>
|
||||
|
||||
<Form.List name={["payers"]}>
|
||||
{(fields, { add, remove }) => {
|
||||
return (
|
||||
<div>
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item key={field.key}>
|
||||
<LayoutFormRow>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.dms.payer.name")}
|
||||
key={`${index}name`}
|
||||
name={[field.name, "name"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t("jobs.fields.dms.payer.account")}
|
||||
key={`${index}account`}
|
||||
name={[field.name, "account"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t("jobs.fields.dms.payer.amount")}
|
||||
key={`${index}amount`}
|
||||
name={[field.name, "amount"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.dms.payer.controlnumber")}
|
||||
key={`${index}controlnumber`}
|
||||
name={[field.name, "controlnumber"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<DeleteFilled
|
||||
onClick={() => {
|
||||
remove(field.name);
|
||||
}}
|
||||
/>
|
||||
</LayoutFormRow>
|
||||
</Form.Item>
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => {
|
||||
add();
|
||||
}}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
{t("general.actions.add")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Form.List>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
@@ -1,24 +1,50 @@
|
||||
import { Button, Popover, Space } from "antd";
|
||||
import { AlertFilled } from "@ant-design/icons";
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Dropdown,
|
||||
Menu,
|
||||
notification,
|
||||
Popover,
|
||||
Space,
|
||||
} from "antd";
|
||||
import parsePhoneNumber from "libphonenumber-js";
|
||||
import moment from "moment";
|
||||
import queryString from "query-string";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
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 PhoneFormatter from "../../utils/PhoneFormatter";
|
||||
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 ScheduleAtChange from "./job-at-change.component";
|
||||
import ScheduleEventColor from "./schedule-event.color.component";
|
||||
import queryString from "query-string";
|
||||
import ScheduleEventNote from "./schedule-event.note.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setScheduleContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "schedule" })),
|
||||
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
||||
setMessage: (text) => dispatch(setMessage(text)),
|
||||
});
|
||||
|
||||
export function ScheduleEventComponent({
|
||||
bodyshop,
|
||||
setMessage,
|
||||
openChatByPhone,
|
||||
event,
|
||||
refetch,
|
||||
handleCancel,
|
||||
@@ -38,7 +64,7 @@ export function ScheduleEventComponent({
|
||||
);
|
||||
|
||||
const popoverContent = (
|
||||
<div>
|
||||
<div style={{ maxWidth: "40vw" }}>
|
||||
{!event.isintake ? (
|
||||
<strong>{event.title}</strong>
|
||||
) : (
|
||||
@@ -75,17 +101,19 @@ export function ScheduleEventComponent({
|
||||
{(event.job && event.job.ownr_ea) || ""}
|
||||
</DataLabel>
|
||||
<DataLabel label={t("jobs.fields.ownr_ph1")}>
|
||||
<PhoneFormatter>
|
||||
{(event.job && event.job.ownr_ph1) || ""}
|
||||
</PhoneFormatter>
|
||||
<ChatOpenButton
|
||||
phone={event.job && event.job.ownr_ph1}
|
||||
jobid={event.job.id}
|
||||
/>
|
||||
</DataLabel>
|
||||
<DataLabel label={t("jobs.fields.alt_transport")}>
|
||||
{(event.job && event.job.alt_transport) || ""}
|
||||
<ScheduleAtChange job={event && event.job} />
|
||||
</DataLabel>
|
||||
<ScheduleEventNote event={event} />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<Divider />
|
||||
<Space wrap>
|
||||
{event.job ? (
|
||||
<Link to={`/manage/jobs/${event.job && event.job.id}`}>
|
||||
@@ -106,23 +134,62 @@ export function ScheduleEventComponent({
|
||||
{t("appointments.actions.preview")}
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
onClick={() => {
|
||||
const Template = TemplateList("job").appointment_reminder;
|
||||
GenerateDocument(
|
||||
{
|
||||
name: Template.key,
|
||||
variables: { id: event.job.id },
|
||||
},
|
||||
{ to: event.job && event.job.ownr_ea, subject: Template.subject },
|
||||
"e",
|
||||
event.job && event.job.id
|
||||
);
|
||||
}}
|
||||
disabled={event.arrived}
|
||||
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
const Template = TemplateList("job").appointment_reminder;
|
||||
GenerateDocument(
|
||||
{
|
||||
name: Template.key,
|
||||
variables: { id: event.job.id },
|
||||
},
|
||||
{
|
||||
to: event.job && event.job.ownr_ea,
|
||||
subject: Template.subject,
|
||||
},
|
||||
"e",
|
||||
event.job && event.job.id
|
||||
);
|
||||
}}
|
||||
disabled={event.arrived}
|
||||
>
|
||||
{t("general.labels.email")}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
const p = parsePhoneNumber(event.job.ownr_ph1, "CA");
|
||||
if (p && p.isValid()) {
|
||||
openChatByPhone({
|
||||
phone_num: p.formatInternational(),
|
||||
jobid: event.job.id,
|
||||
});
|
||||
setMessage(
|
||||
t("appointments.labels.reminder", {
|
||||
shopname: bodyshop.shopname,
|
||||
date: moment(event.start).format("MM/DD/YYYY"),
|
||||
time: moment(event.start).format("HH:MM a"),
|
||||
})
|
||||
);
|
||||
setVisible(false);
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("messaging.error.invalidphone"),
|
||||
});
|
||||
}
|
||||
}}
|
||||
disabled={event.arrived || !bodyshop.messagingservicesid}
|
||||
>
|
||||
{t("general.labels.sms")}
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
{t("appointments.actions.sendreminder")}
|
||||
</Button>
|
||||
<Button>{t("appointments.actions.sendreminder")}</Button>
|
||||
</Dropdown>
|
||||
|
||||
<Button onClick={() => handleCancel(event.id)} disabled={event.arrived}>
|
||||
{t("appointments.actions.cancel")}
|
||||
</Button>
|
||||
@@ -161,6 +228,7 @@ export function ScheduleEventComponent({
|
||||
const RegularEvent = event.isintake ? (
|
||||
<div style={{ display: "flex", flexWrap: "wrap" }}>
|
||||
<Space>
|
||||
{event.note && <AlertFilled className="production-alert" />}
|
||||
<strong>{`${event.job.ro_number || t("general.labels.na")}`}</strong>
|
||||
<span>{`${(event.job && event.job.ownr_fn) || ""} ${
|
||||
(event.job && event.job.ownr_ln) || ""
|
||||
@@ -202,4 +270,7 @@ export function ScheduleEventComponent({
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
export default connect(null, mapDispatchToProps)(ScheduleEventComponent);
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ScheduleEventComponent);
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { EditFilled, SaveFilled } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Input, notification, Space } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import DataLabel from "../data-label/data-label.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
export function ScheduleEventNote({ event }) {
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [note, setNote] = useState(event.note || "");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const toggleEdit = async () => {
|
||||
if (editing) {
|
||||
//Await the update
|
||||
setLoading(true);
|
||||
const result = await updateAppointment({
|
||||
variables: {
|
||||
appid: event.id,
|
||||
app: { note },
|
||||
},
|
||||
});
|
||||
|
||||
if (!!!result.errors) {
|
||||
// notification["success"]({ message: t("appointments.successes.saved") });
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.saving", {
|
||||
error: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
setEditing(false);
|
||||
} else {
|
||||
setEditing(true);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<DataLabel label={t("appointments.fields.note")}>
|
||||
<Space flex>
|
||||
{!editing ? (
|
||||
event.note || ""
|
||||
) : (
|
||||
<Input.TextArea
|
||||
rows={3}
|
||||
value={note}
|
||||
onChange={(e) => setNote(e.target.value)}
|
||||
style={{ maxWidth: "8vw" }}
|
||||
/>
|
||||
)}
|
||||
<Button onClick={toggleEdit} loading={loading}>
|
||||
{editing ? <SaveFilled /> : <EditFilled />}
|
||||
</Button>
|
||||
</Space>
|
||||
</DataLabel>
|
||||
);
|
||||
}
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ScheduleEventNote);
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Card, Table } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
|
||||
export default function JobAuditTrail({ jobId }) {
|
||||
const { t } = useTranslation();
|
||||
const { loading, data } = useQuery(QUERY_AUDIT_TRAIL, {
|
||||
variables: { jobid: jobId },
|
||||
skip: !jobId,
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("audit.fields.created"),
|
||||
dataIndex: "created",
|
||||
key: "created",
|
||||
render: (text, record) => (
|
||||
<DateTimeFormatter>{record.created}</DateTimeFormatter>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("audit.fields.useremail"),
|
||||
dataIndex: "useremail",
|
||||
key: "useremail",
|
||||
},
|
||||
{
|
||||
title: t("audit.fields.operation"),
|
||||
dataIndex: "operation",
|
||||
key: "operation",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Card title={t("jobs.labels.audit")}>
|
||||
<Table
|
||||
loading={loading}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={data ? data.audit_trail : []}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -16,16 +16,21 @@ import {
|
||||
import ConfigFormComponents from "../../../config-form-components/config-form-components.component";
|
||||
import DateTimePicker from "../../../form-date-time-picker/form-date-time-picker.component";
|
||||
import moment from "moment-business-days";
|
||||
import { insertAuditTrail } from "../../../../redux/application/application.actions";
|
||||
import AuditTrailMapping from "../../../../utils/AuditTrailMappings";
|
||||
import { UPDATE_OWNER } from "../../../../graphql/owners.queries";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
});
|
||||
|
||||
export function JobChecklistForm({
|
||||
insertAuditTrail,
|
||||
formItems,
|
||||
bodyshop,
|
||||
currentUser,
|
||||
@@ -37,6 +42,8 @@ export function JobChecklistForm({
|
||||
const [intakeJob] = useMutation(UPDATE_JOB);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [markAptArrived] = useMutation(MARK_LATEST_APPOINTMENT_AS_ARRIVED);
|
||||
const [updateOwner] = useMutation(UPDATE_OWNER);
|
||||
|
||||
const { jobId } = useParams();
|
||||
const history = useHistory();
|
||||
const search = queryString.parse(useLocation().search);
|
||||
@@ -59,6 +66,12 @@ export function JobChecklistForm({
|
||||
production_vars: {
|
||||
...job.production_vars,
|
||||
...values.production_vars,
|
||||
note:
|
||||
values.production_vars &&
|
||||
values.production_vars.note &&
|
||||
values.production_vars.note !== ""
|
||||
? job.production_vars && values.production_vars.note
|
||||
: job.production_vars && job.production_vars.note,
|
||||
},
|
||||
}),
|
||||
...(type === "intake" && {
|
||||
@@ -104,11 +117,40 @@ export function JobChecklistForm({
|
||||
});
|
||||
}
|
||||
}
|
||||
if (type === "intake" && job.owner && job.owner.id) {
|
||||
//Updae Owner Allow to Text
|
||||
const updateOwnerResult = await updateOwner({
|
||||
variables: {
|
||||
ownerId: job.owner.id,
|
||||
owner: { allow_text_message: values.allow_text_message },
|
||||
},
|
||||
});
|
||||
|
||||
if (!!updateOwnerResult.errors) {
|
||||
notification["error"]({
|
||||
message: t("checklist.errors.complete", {
|
||||
error: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
|
||||
if (!!!result.errors) {
|
||||
notification["success"]({ message: t("checklist.successes.completed") });
|
||||
history.push(`/manage/jobs/${jobId}`);
|
||||
|
||||
insertAuditTrail({
|
||||
jobid: jobId,
|
||||
operation: AuditTrailMapping.jobchecklist(
|
||||
type,
|
||||
(type === "deliver" && values.removeFromProduction && false) ||
|
||||
(type === "intake" && values.addToProduction),
|
||||
(type === "intake" && bodyshop.md_ro_statuses.default_arrived) ||
|
||||
(type === "deliver" && bodyshop.md_ro_statuses.default_delivered)
|
||||
),
|
||||
});
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("checklist.errors.complete", {
|
||||
@@ -135,6 +177,7 @@ export function JobChecklistForm({
|
||||
initialValues={{
|
||||
...(type === "intake" && {
|
||||
addToProduction: true,
|
||||
allow_text_message: job.owner && job.owner.allow_text_message,
|
||||
scheduled_completion:
|
||||
(job && job.scheduled_completion) ||
|
||||
moment().businessAdd(
|
||||
@@ -170,6 +213,14 @@ export function JobChecklistForm({
|
||||
>
|
||||
<Switch disabled={readOnly} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="allow_text_message"
|
||||
valuePropName="checked"
|
||||
label={t("checklist.labels.allow_text_message")}
|
||||
disabled={readOnly}
|
||||
>
|
||||
<Switch disabled={readOnly} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="scheduled_completion"
|
||||
label={t("jobs.fields.scheduled_completion")}
|
||||
|
||||
@@ -37,8 +37,8 @@ export function JobEmployeeAssignments({
|
||||
});
|
||||
const [visibility, setVisibility] = useState(false);
|
||||
|
||||
const onChange = (e) => {
|
||||
setAssignment({ ...assignment, employeeid: e });
|
||||
const onChange = (value, option) => {
|
||||
setAssignment({ ...assignment, employeeid: value, name: option.name });
|
||||
};
|
||||
|
||||
const popContent = (
|
||||
@@ -56,7 +56,11 @@ export function JobEmployeeAssignments({
|
||||
}
|
||||
>
|
||||
{bodyshop.employees.map((emp) => (
|
||||
<Select.Option value={emp.id} key={emp.id}>
|
||||
<Select.Option
|
||||
value={emp.id}
|
||||
key={emp.id}
|
||||
name={`${emp.first_name} ${emp.last_name}`}
|
||||
>
|
||||
{`${emp.first_name} ${emp.last_name}`}
|
||||
</Select.Option>
|
||||
))}
|
||||
|
||||
@@ -6,14 +6,34 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { UPDATE_JOB_ASSIGNMENTS } from "../../graphql/jobs.queries";
|
||||
import JobEmployeeAssignmentsComponent from "./job-employee-assignments.component";
|
||||
|
||||
export default function JobEmployeeAssignmentsContainer({ job, refetch }) {
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(JobEmployeeAssignmentsContainer);
|
||||
|
||||
export function JobEmployeeAssignmentsContainer({
|
||||
job,
|
||||
refetch,
|
||||
insertAuditTrail,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [updateJob] = useMutation(UPDATE_JOB_ASSIGNMENTS);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleAdd = async (assignment) => {
|
||||
setLoading(true);
|
||||
const { operation, employeeid } = assignment;
|
||||
const { operation, employeeid, name } = assignment;
|
||||
logImEXEvent("job_assign_employee", { operation });
|
||||
|
||||
let empAssignment = determineFieldName(operation);
|
||||
@@ -23,6 +43,11 @@ export default function JobEmployeeAssignmentsContainer({ job, refetch }) {
|
||||
});
|
||||
if (refetch) refetch();
|
||||
|
||||
insertAuditTrail({
|
||||
jobid: job.id,
|
||||
operation: AuditTrailMapping.jobassignmentchange(operation, name),
|
||||
});
|
||||
|
||||
if (!!result.errors) {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.assigning", {
|
||||
@@ -48,6 +73,10 @@ export default function JobEmployeeAssignmentsContainer({ job, refetch }) {
|
||||
}),
|
||||
});
|
||||
}
|
||||
insertAuditTrail({
|
||||
jobid: job.id,
|
||||
operation: AuditTrailMapping.jobassignmentremoved(operation),
|
||||
});
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ export function JobLineStatusPopup({ bodyshop, jobline, disabled }) {
|
||||
);
|
||||
return (
|
||||
<div
|
||||
style={{ width: "100%", minHeight: "2rem", cursor: "pointer" }}
|
||||
style={{ width: "100%", minHeight: "1rem", cursor: "pointer" }}
|
||||
onClick={() => !disabled && setEditing(true)}
|
||||
>
|
||||
{jobline.status}
|
||||
|
||||
@@ -15,6 +15,7 @@ const JobSearchSelect = (
|
||||
{
|
||||
disabled,
|
||||
convertedOnly = false,
|
||||
notInvoiced = false,
|
||||
notExported = true,
|
||||
clm_no = false,
|
||||
...restProps
|
||||
@@ -30,6 +31,7 @@ const JobSearchSelect = (
|
||||
variables: {
|
||||
...(convertedOnly ? { isConverted: true } : {}),
|
||||
...(notExported ? { notExported: true } : {}),
|
||||
...(notInvoiced ? { notInvoiced: true } : {}),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
|
||||
@@ -6,8 +6,8 @@ import { useTranslation } from "react-i18next";
|
||||
export default function JobAdminDeleteIntake({ job }) {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [updateJob] = useMutation(gql`
|
||||
mutation UPDATE_JOB($jobId: uuid!) {
|
||||
const [deleteIntake] = useMutation(gql`
|
||||
mutation DELETE_INTAKE($jobId: uuid!) {
|
||||
update_jobs_by_pk(
|
||||
pk_columns: { id: $jobId }
|
||||
_set: { intakechecklist: null }
|
||||
@@ -18,9 +18,39 @@ export default function JobAdminDeleteIntake({ job }) {
|
||||
}
|
||||
`);
|
||||
|
||||
const [DELETE_DELIVERY] = useMutation(gql`
|
||||
mutation DELETE_DELIVERY($jobId: uuid!) {
|
||||
update_jobs_by_pk(
|
||||
pk_columns: { id: $jobId }
|
||||
_set: { deliverchecklist: null }
|
||||
) {
|
||||
id
|
||||
deliverchecklist
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const handleDelete = async (values) => {
|
||||
setLoading(true);
|
||||
const result = await updateJob({
|
||||
const result = await deleteIntake({
|
||||
variables: { jobId: job.id },
|
||||
});
|
||||
|
||||
if (!!!result.errors) {
|
||||
notification["success"]({ message: t("jobs.successes.save") });
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.saving", {
|
||||
error: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleDeleteDelivery = async (values) => {
|
||||
setLoading(true);
|
||||
const result = await DELETE_DELIVERY({
|
||||
variables: { jobId: job.id },
|
||||
});
|
||||
|
||||
@@ -34,12 +64,16 @@ export default function JobAdminDeleteIntake({ job }) {
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
//Get the owner details, populate it all back into the job.
|
||||
};
|
||||
|
||||
return (
|
||||
<Button loading={loading} onClick={handleDelete}>
|
||||
{t("jobs.labels.deleteintake")}
|
||||
</Button>
|
||||
<>
|
||||
<Button loading={loading} onClick={handleDelete}>
|
||||
{t("jobs.labels.deleteintake")}
|
||||
</Button>
|
||||
<Button loading={loading} onClick={handleDeleteDelivery}>
|
||||
{t("jobs.labels.deletedelivery")}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import moment from "moment";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
@@ -21,8 +22,8 @@ export default connect(
|
||||
export function JobAdminMarkReexport({ bodyshop, job }) {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [updateJob] = useMutation(gql`
|
||||
mutation UPDATE_JOB($jobId: uuid!) {
|
||||
const [markJobForReexport] = useMutation(gql`
|
||||
mutation MARK_JOB_FOR_REEXPORT($jobId: uuid!) {
|
||||
update_jobs_by_pk(
|
||||
pk_columns: { id: $jobId }
|
||||
_set: { date_exported: null
|
||||
@@ -30,14 +31,84 @@ export function JobAdminMarkReexport({ bodyshop, job }) {
|
||||
}
|
||||
) {
|
||||
id
|
||||
intakechecklist
|
||||
date_exported
|
||||
status
|
||||
date_invoiced
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const handleUpdate = async (values) => {
|
||||
const [markJobExported] = useMutation(gql`
|
||||
mutation MARK_JOB_AS_EXPORTED($jobId: uuid!, $date_exported: timestamptz!) {
|
||||
update_jobs_by_pk(
|
||||
pk_columns: { id: $jobId }
|
||||
_set: { date_exported: $date_exported
|
||||
status: "${bodyshop.md_ro_statuses.default_exported}"
|
||||
}
|
||||
) {
|
||||
id
|
||||
date_exported
|
||||
date_invoiced
|
||||
status
|
||||
}
|
||||
}
|
||||
`);
|
||||
const [markJobUninvoiced] = useMutation(gql`
|
||||
mutation MARK_JOB_AS_UNINVOICED($jobId: uuid!, ) {
|
||||
update_jobs_by_pk(
|
||||
pk_columns: { id: $jobId }
|
||||
_set: { date_exported: null
|
||||
date_invoiced: null
|
||||
status: "${bodyshop.md_ro_statuses.default_delivered}"
|
||||
}
|
||||
) {
|
||||
id
|
||||
date_exported
|
||||
date_invoiced
|
||||
status
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const handleMarkForExport = async () => {
|
||||
setLoading(true);
|
||||
const result = await updateJob({
|
||||
const result = await markJobForReexport({
|
||||
variables: { jobId: job.id },
|
||||
});
|
||||
|
||||
if (!result.errors) {
|
||||
notification["success"]({ message: t("jobs.successes.save") });
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.saving", {
|
||||
error: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleMarkExported = async () => {
|
||||
setLoading(true);
|
||||
const result = await markJobExported({
|
||||
variables: { jobId: job.id, date_exported: moment() },
|
||||
});
|
||||
|
||||
if (!result.errors) {
|
||||
notification["success"]({ message: t("jobs.successes.save") });
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.saving", {
|
||||
error: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleUninvoice = async () => {
|
||||
setLoading(true);
|
||||
const result = await markJobUninvoiced({
|
||||
variables: { jobId: job.id },
|
||||
});
|
||||
|
||||
@@ -51,16 +122,31 @@ export function JobAdminMarkReexport({ bodyshop, job }) {
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
//Get the owner details, populate it all back into the job.
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
loading={loading}
|
||||
disabled={!job.date_exported}
|
||||
onClick={handleUpdate}
|
||||
>
|
||||
{t("jobs.labels.markforreexport")}
|
||||
</Button>
|
||||
<>
|
||||
<Button
|
||||
loading={loading}
|
||||
disabled={!job.date_exported}
|
||||
onClick={handleMarkForExport}
|
||||
>
|
||||
{t("jobs.labels.markforreexport")}
|
||||
</Button>
|
||||
<Button
|
||||
loading={loading}
|
||||
disabled={job.date_exported}
|
||||
onClick={handleMarkExported}
|
||||
>
|
||||
{t("jobs.actions.markasexported")}
|
||||
</Button>
|
||||
<Button
|
||||
loading={loading}
|
||||
disabled={!job.date_invoiced || job.date_exported}
|
||||
onClick={handleUninvoice}
|
||||
>
|
||||
{t("jobs.actions.uninvoice")}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ export function JobsAvailableComponent({
|
||||
title: t("jobs.fields.cieca_id"),
|
||||
dataIndex: "cieca_id",
|
||||
key: "cieca_id",
|
||||
sorter: (a, b) => alphaSort(a, b),
|
||||
sorter: (a, b) => alphaSort(a.cieca_id, b.cieca_id),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "cieca_id" && state.sortedInfo.order,
|
||||
},
|
||||
@@ -68,9 +68,10 @@ export function JobsAvailableComponent({
|
||||
//width: "8%",
|
||||
// onFilter: (value, record) => record.ro_number.includes(value),
|
||||
// filteredValue: state.filteredInfo.text || null,
|
||||
sorter: (a, b) => alphaSort(a, b),
|
||||
sorter: (a, b) =>
|
||||
alphaSort(a.job && a.job.ro_number, b.job && b.job.ro_number),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "cieca_id" && state.sortedInfo.order,
|
||||
state.sortedInfo.columnKey === "job_id" && state.sortedInfo.order,
|
||||
render: (text, record) =>
|
||||
record.job ? (
|
||||
<Link to={`/manage/jobs/${record.job.id}`}>
|
||||
@@ -87,7 +88,7 @@ export function JobsAvailableComponent({
|
||||
dataIndex: "ownr_name",
|
||||
key: "ownr_name",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
||||
sorter: (a, b) => alphaSort(a.ownr_name, b.ownr_name),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ownr_name" && state.sortedInfo.order,
|
||||
},
|
||||
|
||||
@@ -25,10 +25,12 @@ import {
|
||||
import { INSERT_NEW_JOB, UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import { INSERT_NEW_NOTE } from "../../graphql/notes.queries";
|
||||
import { SEARCH_VEHICLE_BY_VIN } from "../../graphql/vehicles.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component";
|
||||
import JobsFindModalContainer from "../jobs-find-modal/jobs-find-modal.container";
|
||||
@@ -42,8 +44,15 @@ const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser,
|
||||
});
|
||||
|
||||
export function JobsAvailableContainer({ bodyshop, currentUser }) {
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
});
|
||||
export function JobsAvailableContainer({
|
||||
bodyshop,
|
||||
currentUser,
|
||||
insertAuditTrail,
|
||||
}) {
|
||||
const { loading, error, data, refetch } = useQuery(QUERY_AVAILABLE_JOBS, {
|
||||
fetchPolicy: "network-only",
|
||||
});
|
||||
@@ -66,7 +75,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
|
||||
const client = useApolloClient();
|
||||
|
||||
const estDataLazyLoad = useLazyQuery(QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK);
|
||||
const [loadEstData, estData] = estDataLazyLoad;
|
||||
const [loadEstData, estDataRaw] = estDataLazyLoad;
|
||||
|
||||
const importOptionsState = useState({ overrideHeaders: false });
|
||||
const importOptions = importOptionsState[0];
|
||||
@@ -79,13 +88,9 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
|
||||
setOwnerModalVisible(false);
|
||||
setInsertLoading(true);
|
||||
|
||||
if (
|
||||
!(
|
||||
estData.data &&
|
||||
estData.data.available_jobs_by_pk &&
|
||||
estData.data.available_jobs_by_pk.est_data
|
||||
)
|
||||
) {
|
||||
const estData = replaceEmpty(estDataRaw.data.available_jobs_by_pk);
|
||||
|
||||
if (!(estData && estData.est_data)) {
|
||||
//We don't have the right data. Error!
|
||||
setInsertLoading(false);
|
||||
notification["error"]({
|
||||
@@ -97,28 +102,25 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
|
||||
const newTotals = (
|
||||
await Axios.post("/job/totals", {
|
||||
job: {
|
||||
...estData.data.available_jobs_by_pk.est_data,
|
||||
joblines: estData.data.available_jobs_by_pk.est_data.joblines.data,
|
||||
...estData.est_data,
|
||||
joblines: estData.est_data.joblines.data,
|
||||
},
|
||||
})
|
||||
).data;
|
||||
|
||||
let existingVehicles;
|
||||
if (
|
||||
estData.data.available_jobs_by_pk.est_data.vehicle &&
|
||||
estData.data.available_jobs_by_pk.est_data.vin
|
||||
) {
|
||||
if (estData.est_data.vehicle && estData.est_data.vin) {
|
||||
//There's vehicle data, need to double check the VIN.
|
||||
existingVehicles = await client.query({
|
||||
query: SEARCH_VEHICLE_BY_VIN,
|
||||
variables: {
|
||||
vin: estData.data.available_jobs_by_pk.est_data.vehicle.data.v_vin,
|
||||
vin: estData.est_data.vehicle.data.v_vin,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const newJob = {
|
||||
...estData.data.available_jobs_by_pk.est_data,
|
||||
...estData.est_data,
|
||||
clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
|
||||
owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"),
|
||||
job_totals: newTotals,
|
||||
@@ -157,8 +159,13 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
|
||||
});
|
||||
//Job has been inserted. Clean up the available jobs record.
|
||||
|
||||
insertAuditTrail({
|
||||
jobid: r.data.insert_jobs.returning[0].id,
|
||||
operation: AuditTrailMapping.jobimported(),
|
||||
});
|
||||
|
||||
deleteJob({
|
||||
variables: { id: estData.data.available_jobs_by_pk.id },
|
||||
variables: { id: estData.id },
|
||||
}).then((r) => {
|
||||
refetch();
|
||||
setInsertLoading(false);
|
||||
@@ -180,14 +187,8 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
|
||||
|
||||
setJobModalVisible(false);
|
||||
setInsertLoading(true);
|
||||
|
||||
if (
|
||||
!(
|
||||
estData.data &&
|
||||
estData.data.available_jobs_by_pk &&
|
||||
estData.data.available_jobs_by_pk.est_data
|
||||
)
|
||||
) {
|
||||
const estData = replaceEmpty(estDataRaw.data.available_jobs_by_pk);
|
||||
if (!(estData && estData.est_data)) {
|
||||
//We don't have the right data. Error!
|
||||
setInsertLoading(false);
|
||||
notification["error"]({
|
||||
@@ -195,18 +196,19 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
|
||||
});
|
||||
} else {
|
||||
//create upsert job
|
||||
let supp = _.cloneDeep(estData.data.available_jobs_by_pk.est_data);
|
||||
let supp = _.cloneDeep(estData.est_data);
|
||||
|
||||
delete supp.owner;
|
||||
delete supp.vehicle;
|
||||
if (importOptions.overrideHeaders) {
|
||||
delete supp.ins_co_nm;
|
||||
if (!importOptions.overrideHeaders) {
|
||||
HeaderFields.forEach((item) => delete supp[item]);
|
||||
}
|
||||
|
||||
let suppDelta = await GetSupplementDelta(
|
||||
client,
|
||||
selectedJob,
|
||||
estData.data.available_jobs_by_pk.est_data.joblines.data
|
||||
estData.est_data.joblines.data
|
||||
);
|
||||
|
||||
delete supp.joblines;
|
||||
@@ -265,7 +267,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
|
||||
//Job has been inserted. Clean up the available jobs record.
|
||||
|
||||
deleteJob({
|
||||
variables: { id: estData.data.available_jobs_by_pk.id },
|
||||
variables: { id: estData.id },
|
||||
}).then((r) => {
|
||||
refetch();
|
||||
setInsertLoading(false);
|
||||
@@ -283,17 +285,21 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
|
||||
],
|
||||
},
|
||||
});
|
||||
insertAuditTrail({
|
||||
jobid: selectedJob,
|
||||
operation: AuditTrailMapping.jobsupplement(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const owner =
|
||||
estData.data &&
|
||||
estData.data.available_jobs_by_pk &&
|
||||
estData.data.available_jobs_by_pk.est_data &&
|
||||
estData.data.available_jobs_by_pk.est_data.owner &&
|
||||
estData.data.available_jobs_by_pk.est_data.owner.data &&
|
||||
!estData.data.available_jobs_by_pk.issupplement
|
||||
? estData.data.available_jobs_by_pk.est_data.owner.data
|
||||
estDataRaw.data &&
|
||||
estDataRaw.data.available_jobs_by_pk &&
|
||||
estDataRaw.data.available_jobs_by_pk.est_data &&
|
||||
estDataRaw.data.available_jobs_by_pk.est_data.owner &&
|
||||
estDataRaw.data.available_jobs_by_pk.est_data.owner.data &&
|
||||
!estDataRaw.data.available_jobs_by_pk.issupplement
|
||||
? estDataRaw.data.available_jobs_by_pk.est_data.owner.data
|
||||
: null;
|
||||
|
||||
const onOwnerModalCancel = () => {
|
||||
@@ -331,8 +337,8 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
|
||||
message={t("jobs.labels.creating_new_job")}
|
||||
>
|
||||
<OwnerFindModalContainer
|
||||
loading={estData.loading}
|
||||
error={estData.error}
|
||||
loading={estDataRaw.loading}
|
||||
error={estDataRaw.error}
|
||||
owner={owner}
|
||||
selectedOwner={selectedOwner}
|
||||
setSelectedOwner={setSelectedOwner}
|
||||
@@ -341,8 +347,8 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
|
||||
onCancel={onOwnerModalCancel}
|
||||
/>
|
||||
<JobsFindModalContainer
|
||||
loading={estData.loading}
|
||||
error={estData.error}
|
||||
loading={estDataRaw.loading}
|
||||
error={estDataRaw.error}
|
||||
selectedJob={selectedJob}
|
||||
setSelectedJob={setSelectedJob}
|
||||
importOptionsState={importOptionsState}
|
||||
@@ -368,4 +374,16 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
|
||||
</LoadingSpinner>
|
||||
);
|
||||
}
|
||||
export default connect(mapStateToProps, null)(JobsAvailableContainer);
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(JobsAvailableContainer);
|
||||
|
||||
function replaceEmpty(someObj, replaceValue = null) {
|
||||
const replacer = (key, value) => (value === "" ? replaceValue : value);
|
||||
//^ because you seem to want to replace (strings) "null" or "undefined" too
|
||||
console.log(someObj);
|
||||
const temp = JSON.stringify(someObj, replacer);
|
||||
console.log(`temp`, temp);
|
||||
return JSON.parse(temp);
|
||||
}
|
||||
|
||||
@@ -6,18 +6,21 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { UPDATE_JOB_STATUS } from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
jobRO: selectJobReadOnly,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
});
|
||||
|
||||
export function JobsChangeStatus({ job, bodyshop, jobRO }) {
|
||||
export function JobsChangeStatus({ job, bodyshop, jobRO, insertAuditTrail }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [availableStatuses, setAvailableStatuses] = useState([]);
|
||||
@@ -29,6 +32,10 @@ export function JobsChangeStatus({ job, bodyshop, jobRO }) {
|
||||
})
|
||||
.then((r) => {
|
||||
notification["success"]({ message: t("jobs.successes.save") });
|
||||
insertAuditTrail({
|
||||
jobid: job.id,
|
||||
operation: AuditTrailMapping.jobstatuschange(status),
|
||||
});
|
||||
// refetch();
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button } from "antd";
|
||||
import { Button, Dropdown, Menu } from "antd";
|
||||
import _ from "lodash";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -12,11 +12,8 @@ const mapStateToProps = createStructuredSelector({
|
||||
|
||||
export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) {
|
||||
const { t } = useTranslation();
|
||||
const handleAllocate = () => {
|
||||
logImEXEvent("jobs_close_allocate_auto");
|
||||
|
||||
const { defaults } = bodyshop.md_responsibility_centers;
|
||||
|
||||
const handleAllocate = (defaults) => {
|
||||
form.setFieldsValue({
|
||||
joblines: joblines.map((jl) => {
|
||||
const ret = _.cloneDeep(jl);
|
||||
@@ -32,7 +29,7 @@ export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) {
|
||||
}
|
||||
//Verify that this is also manually updated in server/job-costing
|
||||
if (!jl.part_type && !jl.mod_lbr_ty) {
|
||||
const lineDesc = jl.line_desc.toLowerCase();
|
||||
const lineDesc = jl.line_desc ? jl.line_desc.toLowerCase() : "";
|
||||
if (lineDesc.includes("shop materials")) {
|
||||
ret.profitcenter_part = defaults.profits["MASH"];
|
||||
} else if (lineDesc.includes("paint/materials")) {
|
||||
@@ -48,8 +45,36 @@ export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) {
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Button onClick={handleAllocate} disabled={disabled}>
|
||||
const handleAutoAllocateClick = () => {
|
||||
logImEXEvent("jobs_close_allocate_auto");
|
||||
|
||||
const { defaults } = bodyshop.md_responsibility_centers;
|
||||
handleAllocate(defaults);
|
||||
};
|
||||
|
||||
const handleMenuClick = ({ item, key, keyPath, domEvent }) => {
|
||||
logImEXEvent("jobs_close_allocate_auto_dms");
|
||||
handleAllocate(
|
||||
bodyshop.md_responsibility_centers.dms_defaults.find(
|
||||
(x) => x.name === key
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const overlay = bodyshop.cdk_dealerid && (
|
||||
<Menu onClick={handleMenuClick}>
|
||||
{bodyshop.md_responsibility_centers.dms_defaults.map((mapping) => (
|
||||
<Menu.Item key={mapping.name}>{mapping.name}</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return bodyshop.cdk_dealerid ? (
|
||||
<Dropdown overlay={overlay}>
|
||||
<Button disabled={disabled}>{t("jobs.actions.dmsautoallocate")}</Button>
|
||||
</Dropdown>
|
||||
) : (
|
||||
<Button onClick={handleAutoAllocateClick} disabled={disabled}>
|
||||
{t("jobs.actions.autoallocate")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -24,6 +24,7 @@ export function JobsCloseExportButton({
|
||||
currentUser,
|
||||
jobId,
|
||||
disabled,
|
||||
setSelectedJobs,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [updateJob] = useMutation(UPDATE_JOB);
|
||||
@@ -147,6 +148,11 @@ export function JobsCloseExportButton({
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (setSelectedJobs) {
|
||||
setSelectedJobs((selectedJobs) => {
|
||||
return selectedJobs.filter((i) => i !== jobId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
|
||||
@@ -13,8 +13,10 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { CONVERT_JOB_TO_RO } from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -22,10 +24,17 @@ const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
});
|
||||
|
||||
export function JobsConvertButton({ bodyshop, job, refetch, jobRO }) {
|
||||
export function JobsConvertButton({
|
||||
bodyshop,
|
||||
job,
|
||||
refetch,
|
||||
jobRO,
|
||||
insertAuditTrail,
|
||||
}) {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [mutationConvertJob] = useMutation(CONVERT_JOB_TO_RO);
|
||||
@@ -43,6 +52,14 @@ export function JobsConvertButton({ bodyshop, job, refetch, jobRO }) {
|
||||
notification["success"]({
|
||||
message: t("jobs.successes.converted"),
|
||||
});
|
||||
|
||||
insertAuditTrail({
|
||||
jobid: job.id,
|
||||
operation: AuditTrailMapping.jobconverted(
|
||||
res.data.update_jobs.returning[0].ro_number
|
||||
),
|
||||
});
|
||||
|
||||
setVisible(false);
|
||||
}
|
||||
setLoading(false);
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { notification } from "antd";
|
||||
import i18n from "i18next";
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { store } from "../../redux/store";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
|
||||
export default function AddToProduction(
|
||||
apolloClient,
|
||||
@@ -21,6 +24,13 @@ export default function AddToProduction(
|
||||
notification["success"]({
|
||||
message: i18n.t("jobs.successes.save"),
|
||||
});
|
||||
|
||||
store.dispatch(
|
||||
insertAuditTrail({
|
||||
jobid: jobId,
|
||||
operation: AuditTrailMapping.jobinproductionchange(!remove),
|
||||
})
|
||||
);
|
||||
if (completionCallback) completionCallback();
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
@@ -142,7 +142,10 @@ export function JobsDetailHeaderActions({
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key="entertimetickets"
|
||||
disabled={!job.converted}
|
||||
disabled={
|
||||
!job.converted ||
|
||||
(!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced)
|
||||
}
|
||||
onClick={() => {
|
||||
logImEXEvent("job_header_enter_time_ticekts");
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { alphaSort } from "../../utils/sorters";
|
||||
import LaborAllocationsAdjustmentEdit from "../labor-allocations-adjustment-edit/labor-allocations-adjustment-edit.component";
|
||||
import "./labor-allocations-table.styles.scss";
|
||||
import { CalculateAllocationsTotals } from "./labor-allocations-table.utility";
|
||||
|
||||
import _ from "lodash";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
technician: selectTechnician,
|
||||
@@ -113,7 +113,7 @@ export function LaborAllocationsTable({
|
||||
color: record.difference >= 0 ? "green" : "red",
|
||||
}}
|
||||
>
|
||||
{record.difference}
|
||||
{_.round(record.difference, 1)}
|
||||
</strong>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -9,6 +9,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { UPDATE_JOB_LINE_STATUS } from "../../graphql/jobs-lines.queries";
|
||||
import { INSERT_NEW_PARTS_ORDERS } from "../../graphql/parts-orders.queries";
|
||||
import { QUERY_ALL_VENDORS_FOR_ORDER } from "../../graphql/vendors.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { setEmailOptions } from "../../redux/email/email.actions";
|
||||
import {
|
||||
setModalContext,
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
@@ -36,6 +38,8 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("partsOrder")),
|
||||
setBillEnterContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "billEnter" })),
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
});
|
||||
|
||||
export function PartsOrderModalContainer({
|
||||
@@ -45,6 +49,7 @@ export function PartsOrderModalContainer({
|
||||
bodyshop,
|
||||
setEmailOptions,
|
||||
setBillEnterContext,
|
||||
insertAuditTrail,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -109,6 +114,18 @@ export function PartsOrderModalContainer({
|
||||
: bodyshop.md_order_statuses.default_ordered || "Ordered*",
|
||||
},
|
||||
});
|
||||
|
||||
insertAuditTrail({
|
||||
jobid: jobId,
|
||||
operation: isReturn
|
||||
? AuditTrailMapping.jobspartsreturn(
|
||||
insertResult.data.insert_parts_orders.returning[0].order_number
|
||||
)
|
||||
: AuditTrailMapping.jobspartsorder(
|
||||
insertResult.data.insert_parts_orders.returning[0].order_number
|
||||
),
|
||||
});
|
||||
|
||||
if (!!jobLinesResult.errors) {
|
||||
notification["error"]({
|
||||
message: t("parts_orders.errors.creating"),
|
||||
|
||||
@@ -25,6 +25,7 @@ export function PayableExportButton({
|
||||
billId,
|
||||
disabled,
|
||||
loadingCallback,
|
||||
setSelectedBills,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [updateBill] = useMutation(UPDATE_BILLS);
|
||||
@@ -142,6 +143,11 @@ export function PayableExportButton({
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (setSelectedBills) {
|
||||
setSelectedBills((selectedBills) => {
|
||||
return selectedBills.filter((i) => i !== billId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!!loadingCallback) loadingCallback(false);
|
||||
|
||||
@@ -24,6 +24,7 @@ export function PaymentExportButton({
|
||||
paymentId,
|
||||
disabled,
|
||||
loadingCallback,
|
||||
setSelectedPayments,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [updatePayment] = useMutation(UPDATE_PAYMENTS);
|
||||
@@ -141,6 +142,12 @@ export function PaymentExportButton({
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
if (setSelectedPayments) {
|
||||
setSelectedPayments((selectedBills) => {
|
||||
return selectedBills.filter((i) => i !== paymentId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!!loadingCallback) loadingCallback(false);
|
||||
|
||||
@@ -14,12 +14,25 @@ import IndefiniteLoading from "../indefinite-loading/indefinite-loading.componen
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import ProductionBoardFilters from "../production-board-filters/production-board-filters.component";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
technician: selectTechnician,
|
||||
});
|
||||
|
||||
export function ProductionBoardKanbanComponent({ data, bodyshop, technician }) {
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
});
|
||||
|
||||
export function ProductionBoardKanbanComponent({
|
||||
data,
|
||||
bodyshop,
|
||||
technician,
|
||||
insertAuditTrail,
|
||||
}) {
|
||||
const [boardLanes, setBoardLanes] = useState({
|
||||
columns: [{ id: "Loading...", title: "Loading...", cards: [] }],
|
||||
});
|
||||
@@ -104,6 +117,11 @@ export function ProductionBoardKanbanComponent({ data, bodyshop, technician }) {
|
||||
newChildCardNewParent
|
||||
),
|
||||
});
|
||||
insertAuditTrail({
|
||||
jobid: card.id,
|
||||
operation: AuditTrailMapping.jobstatuschange(destination.toColumnId),
|
||||
});
|
||||
|
||||
if (update.errors) {
|
||||
notification["error"]({
|
||||
message: t("production.errors.boardupdate", {
|
||||
@@ -130,4 +148,7 @@ export function ProductionBoardKanbanComponent({ data, bodyshop, technician }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default connect(mapStateToProps, null)(ProductionBoardKanbanComponent);
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ProductionBoardKanbanComponent);
|
||||
|
||||
@@ -16,24 +16,32 @@ import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
|
||||
const iconStyle = { marginLeft: ".3rem" };
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
});
|
||||
|
||||
export function ProductionListEmpAssignment({ bodyshop, record, type }) {
|
||||
export function ProductionListEmpAssignment({
|
||||
insertAuditTrail,
|
||||
bodyshop,
|
||||
record,
|
||||
type,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [updateJob] = useMutation(UPDATE_JOB);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleAdd = async (assignment) => {
|
||||
setLoading(true);
|
||||
const { operation, employeeid } = assignment;
|
||||
const { operation, employeeid, name } = assignment;
|
||||
logImEXEvent("job_assign_employee", { operation });
|
||||
|
||||
let empAssignment = determineFieldName(operation);
|
||||
@@ -44,6 +52,11 @@ export function ProductionListEmpAssignment({ bodyshop, record, type }) {
|
||||
awaitRefetchQueries: true,
|
||||
});
|
||||
|
||||
insertAuditTrail({
|
||||
jobid: record.id,
|
||||
operation: AuditTrailMapping.jobassignmentchange(empAssignment, name),
|
||||
});
|
||||
|
||||
if (!!result.errors) {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.assigning", {
|
||||
@@ -64,6 +77,11 @@ export function ProductionListEmpAssignment({ bodyshop, record, type }) {
|
||||
awaitRefetchQueries: true,
|
||||
});
|
||||
|
||||
insertAuditTrail({
|
||||
jobid: record.id,
|
||||
operation: AuditTrailMapping.jobassignmentremoved(empAssignment),
|
||||
});
|
||||
|
||||
if (!!result.errors) {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.assigning", {
|
||||
@@ -80,8 +98,8 @@ export function ProductionListEmpAssignment({ bodyshop, record, type }) {
|
||||
});
|
||||
|
||||
const [visibility, setVisibility] = useState(false);
|
||||
const onChange = (e) => {
|
||||
setAssignment({ ...assignment, employeeid: e });
|
||||
const onChange = (e, option) => {
|
||||
setAssignment({ ...assignment, employeeid: e, name: option.name });
|
||||
};
|
||||
|
||||
const popContent = (
|
||||
@@ -99,7 +117,11 @@ export function ProductionListEmpAssignment({ bodyshop, record, type }) {
|
||||
}
|
||||
>
|
||||
{bodyshop.employees.map((emp) => (
|
||||
<Select.Option value={emp.id} key={emp.id}>
|
||||
<Select.Option
|
||||
value={emp.id}
|
||||
key={emp.id}
|
||||
name={`${emp.first_name} ${emp.last_name}`}
|
||||
>
|
||||
{`${emp.first_name} ${emp.last_name}`}
|
||||
</Select.Option>
|
||||
))}
|
||||
|
||||
@@ -6,12 +6,21 @@ import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
export function ProductionListColumnStatus({ record, bodyshop }) {
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
});
|
||||
export function ProductionListColumnStatus({
|
||||
record,
|
||||
bodyshop,
|
||||
insertAuditTrail,
|
||||
}) {
|
||||
const [updateJob] = useMutation(UPDATE_JOB);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
@@ -28,6 +37,11 @@ export function ProductionListColumnStatus({ record, bodyshop }) {
|
||||
},
|
||||
},
|
||||
});
|
||||
insertAuditTrail({
|
||||
jobid: record.id,
|
||||
operation: AuditTrailMapping.jobstatuschange(key),
|
||||
});
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
@@ -52,4 +66,7 @@ export function ProductionListColumnStatus({ record, bodyshop }) {
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
export default connect(mapStateToProps, null)(ProductionListColumnStatus);
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ProductionListColumnStatus);
|
||||
|
||||
@@ -25,6 +25,7 @@ export function ProductionListTable({
|
||||
currentUser,
|
||||
state,
|
||||
setColumns,
|
||||
setState,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [updateDefaultProdView] = useMutation(UPDATE_ACTIVE_PROD_LIST_VIEW);
|
||||
@@ -43,6 +44,10 @@ export function ProductionListTable({
|
||||
};
|
||||
})
|
||||
);
|
||||
setState(
|
||||
bodyshop.production_config.filter((pc) => pc.name === value)[0].columns
|
||||
.tableState
|
||||
);
|
||||
|
||||
const assoc = bodyshop.associations.find(
|
||||
(a) => a.useremail === currentUser.email
|
||||
@@ -77,6 +82,8 @@ export function ProductionListTable({
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
setState(bodyshop.production_config[0].columns.tableState);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -41,9 +41,9 @@ export function ProductionListTable({
|
||||
|
||||
const [state, setState] = useState(
|
||||
(bodyshop.production_config &&
|
||||
bodyshop.production_config.find((p) => p.name === defaultView)
|
||||
?.tableState) ||
|
||||
bodyshop.production_config[0]?.tableState || {
|
||||
bodyshop.production_config.find((p) => p.name === defaultView)?.columns
|
||||
.tableState) ||
|
||||
bodyshop.production_config[0]?.columns.tableState || {
|
||||
sortedInfo: {},
|
||||
filteredInfo: { text: "" },
|
||||
}
|
||||
@@ -173,6 +173,7 @@ export function ProductionListTable({
|
||||
|
||||
<ProductionListTableViewSelect
|
||||
state={state}
|
||||
setState={setState}
|
||||
setColumns={setColumns}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { Button, Form, Input } from "antd";
|
||||
import { Button, Form, Input, notification } from "antd";
|
||||
import { LockOutlined } from "@ant-design/icons";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { updateUserDetails } from "../../redux/user/user.actions";
|
||||
import { selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import {
|
||||
logImEXEvent,
|
||||
updateCurrentPassword,
|
||||
} from "../../firebase/firebase.utils";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
@@ -28,33 +33,96 @@ export default connect(
|
||||
});
|
||||
};
|
||||
|
||||
const handleChangePassword = async ({ password }) => {
|
||||
logImEXEvent("password_update");
|
||||
try {
|
||||
await updateCurrentPassword(password);
|
||||
notification.success({
|
||||
message: t("user.successess.passwordchanged"),
|
||||
});
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form
|
||||
onFinish={handleFinish}
|
||||
autoComplete={"no"}
|
||||
initialValues={currentUser}
|
||||
layout="vertical"
|
||||
>
|
||||
<Form.Item
|
||||
label={t("user.fields.displayname")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name="displayName"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("user.fields.photourl")} name="photoURL">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<LayoutFormRow>
|
||||
<Form.Item
|
||||
label={t("user.fields.displayname")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name="displayName"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("user.fields.photourl")} name="photoURL">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
|
||||
<Button type="primary" key="submit" htmlType="submit">
|
||||
{t("user.actions.updateprofile")}
|
||||
</Button>
|
||||
</Form>
|
||||
<Form
|
||||
onFinish={handleChangePassword}
|
||||
autoComplete={"no"}
|
||||
initialValues={currentUser}
|
||||
layout="vertical"
|
||||
>
|
||||
<LayoutFormRow>
|
||||
<Form.Item label={t("general.labels.newpassword")} name="password">
|
||||
<Input
|
||||
prefix={<LockOutlined />}
|
||||
type="password"
|
||||
placeholder={t("general.labels.password")}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("general.labels.confirmpassword")}
|
||||
name="password-confirm"
|
||||
dependencies={["password"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
({ getFieldValue }) => ({
|
||||
validator(rule, value) {
|
||||
if (!value || getFieldValue("password") === value) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(
|
||||
t("general.labels.passwordsdonotmatch")
|
||||
);
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
prefix={<LockOutlined />}
|
||||
type="password"
|
||||
placeholder={t("general.labels.password")}
|
||||
/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<Button type="primary" key="submit" htmlType="submit">
|
||||
{t("user.actions.changepassword")}
|
||||
</Button>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -61,8 +61,12 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
|
||||
{
|
||||
name: values.key,
|
||||
variables: {
|
||||
...(start ? { start: moment(start).format("YYYY-MM-DD") } : {}),
|
||||
...(end ? { end: moment(end).format("YYYY-MM-DD") } : {}),
|
||||
...(start
|
||||
? { start: moment(start).startOf("day").format("YYYY-MM-DD") }
|
||||
: {}),
|
||||
...(end
|
||||
? { end: moment(end).endOf("day").format("YYYY-MM-DD") }
|
||||
: {}),
|
||||
...(id ? { id: id } : {}),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
import { Button, Card, Col, Form, Row, Select, Switch } from "antd";
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
Form,
|
||||
Input,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Typography,
|
||||
} from "antd";
|
||||
import axios from "axios";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
@@ -91,31 +101,34 @@ export function ScheduleJobModalComponent({
|
||||
<DateTimePicker onlyFuture />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<Card title={t("appointments.labels.smartscheduling")}>
|
||||
|
||||
<Typography.Title level={4}>
|
||||
{t("appointments.labels.smartscheduling")}
|
||||
</Typography.Title>
|
||||
|
||||
{
|
||||
// smartOptions.length > 0 && (
|
||||
// <div>{t("appointments.labels.suggesteddates")}</div>
|
||||
// )
|
||||
}
|
||||
<Space wrap>
|
||||
<Button onClick={handleSmartScheduling} loading={loading}>
|
||||
{t("appointments.actions.calculate")}
|
||||
</Button>
|
||||
{smartOptions.length > 0 && (
|
||||
<div>{t("appointments.labels.suggesteddates")}</div>
|
||||
)}
|
||||
<div
|
||||
className="imex-flex-row imex-flex-row__flex-space-around"
|
||||
style={{ flexWrap: "wrap" }}
|
||||
>
|
||||
{smartOptions.map((d, idx) => (
|
||||
<Button
|
||||
className="imex-flex-row__margin"
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
form.setFieldsValue({ start: new moment(d).add(8, "hours") });
|
||||
handleDateBlur();
|
||||
}}
|
||||
>
|
||||
<DateFormatter>{d}</DateFormatter>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
{smartOptions.map((d, idx) => (
|
||||
<Button
|
||||
className="imex-flex-row__margin"
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
form.setFieldsValue({ start: new moment(d).add(8, "hours") });
|
||||
handleDateBlur();
|
||||
}}
|
||||
>
|
||||
<DateFormatter>{d}</DateFormatter>
|
||||
</Button>
|
||||
))}
|
||||
</Space>
|
||||
|
||||
<LayoutFormRow grow>
|
||||
<Form.Item
|
||||
name="notifyCustomer"
|
||||
@@ -124,12 +137,9 @@ export function ScheduleJobModalComponent({
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate>
|
||||
{() => (
|
||||
<Form.Item name="email" label={t("jobs.fields.ownr_ea")}>
|
||||
<EmailInput disabled={!form.getFieldValue("notifyCustomer")} />
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Form.Item name="email" label={t("jobs.fields.ownr_ea")}>
|
||||
<EmailInput disabled={!form.getFieldValue("notifyCustomer")} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow>
|
||||
@@ -158,6 +168,9 @@ export function ScheduleJobModalComponent({
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item name={"note"} label={t("appointments.fields.note")}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
{t("appointments.labels.history")}
|
||||
<ScheduleExistingAppointmentsList
|
||||
|
||||
@@ -105,6 +105,7 @@ export function ScheduleJobModalContainer({
|
||||
start: moment(values.start),
|
||||
end: moment(values.start).add(bodyshop.appt_length || 60, "minutes"),
|
||||
color: values.color,
|
||||
note:values.note
|
||||
},
|
||||
jobId: jobId,
|
||||
altTransport: values.alt_transport,
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
|
||||
|
||||
import _ from "lodash";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
@@ -52,17 +52,22 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
|
||||
|
||||
const theValue = {
|
||||
date: moment(val).format("D dd"),
|
||||
paintHrs: dayhrs.painthrs,
|
||||
bodyHrs: dayhrs.bodyhrs,
|
||||
accTargetHrs: Utils.AsOfDateTargetHours(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget +
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
val
|
||||
paintHrs: _.round(dayhrs.painthrs, 1),
|
||||
bodyHrs: _.round(dayhrs.bodyhrs, 1),
|
||||
accTargetHrs: _.round(
|
||||
Utils.AsOfDateTargetHours(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget +
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
val
|
||||
),
|
||||
1
|
||||
),
|
||||
accHrs:
|
||||
accHrs: _.round(
|
||||
acc.length > 0
|
||||
? acc[acc.length - 1].accHrs + dayhrs.painthrs + dayhrs.bodyhrs
|
||||
: dayhrs.painthrs + dayhrs.bodyhrs,
|
||||
1
|
||||
),
|
||||
};
|
||||
|
||||
return [...acc, theValue];
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Button, Card, Tabs } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import ShopInfoGeneral from "./shop-info.general.component";
|
||||
import ShopInfoIntakeChecklistComponent from "./shop-info.intake.component";
|
||||
import ShopInfoLaborRates from "./shop-info.laborrates.component";
|
||||
@@ -11,7 +14,15 @@ import ShopInfoROStatusComponent from "./shop-info.rostatus.component";
|
||||
import ShopInfoSchedulingComponent from "./shop-info.scheduling.component";
|
||||
import ShopInfoSpeedPrint from "./shop-info.speedprint.component";
|
||||
|
||||
export default function ShopInfoComponent({ form, saveLoading }) {
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoComponent);
|
||||
|
||||
export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Card
|
||||
@@ -53,6 +64,7 @@ export default function ShopInfoComponent({ form, saveLoading }) {
|
||||
>
|
||||
<ShopInfoResponsibilityCenterComponent form={form} />
|
||||
</Tabs.TabPane>
|
||||
|
||||
<Tabs.TabPane key="checklists" tab={t("bodyshop.labels.checklists")}>
|
||||
<ShopInfoIntakeChecklistComponent form={form} />
|
||||
</Tabs.TabPane>
|
||||
|
||||
@@ -444,6 +444,13 @@ export default function ShopInfoGeneral({ form }) {
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["tt_allow_post_to_invoiced"]}
|
||||
label={t("bodyshop.fields.tt_allow_post_to_invoiced")}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.labels.messagingpresets")}>
|
||||
<Form.List name={["md_messaging_presets"]}>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -30,7 +30,10 @@ export function TechClockInComponent({ form, bodyshop, technician }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<JobSearchSelect />
|
||||
<JobSearchSelect
|
||||
convertedOnly={!bodyshop.tt_allow_post_to_invoiced}
|
||||
notExported={!bodyshop.tt_allow_post_to_invoiced}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Card, Space, Table } from "antd";
|
||||
import { EditFilled } from "@ant-design/icons";
|
||||
import { Card, Space, Table } from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
selectAuthLevel,
|
||||
@@ -16,8 +17,6 @@ import RbacWrapper, {
|
||||
HasRbacAccess,
|
||||
} from "../rbac-wrapper/rbac-wrapper.component";
|
||||
import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
authLevel: selectAuthLevel,
|
||||
@@ -105,7 +104,8 @@ export function TimeTicketList({
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number),
|
||||
sorter: (a, b) =>
|
||||
alphaSort(a.job && a.job.ro_number, b.job && b.job.ro_number),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||
render: (text, record) =>
|
||||
@@ -267,8 +267,12 @@ export function TimeTicketList({
|
||||
<Table.Summary.Cell />
|
||||
<Table.Summary.Cell />
|
||||
<Table.Summary.Cell />
|
||||
<Table.Summary.Cell>{totals.productivehrs}</Table.Summary.Cell>
|
||||
<Table.Summary.Cell>{totals.actualhrs}</Table.Summary.Cell>
|
||||
<Table.Summary.Cell>
|
||||
{totals.productivehrs.toFixed(1)}
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell>
|
||||
{totals.actualhrs.toFixed(1)}
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell>
|
||||
{totals.actualhrs === 0 || !totals.actualhrs
|
||||
? "∞"
|
||||
|
||||
@@ -48,7 +48,9 @@ export function TimeTicketModalComponent({
|
||||
{emps &&
|
||||
emps.rates.map((item) => (
|
||||
<Select.Option key={item.cost_center}>
|
||||
{item.cost_center}
|
||||
{item.cost_center === "timetickets.labels.shift"
|
||||
? t(item.cost_center)
|
||||
: item.cost_center}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
@@ -82,7 +84,10 @@ export function TimeTicketModalComponent({
|
||||
},
|
||||
]}
|
||||
>
|
||||
<JobSearchSelect convertedOnly notExported={false} />
|
||||
<JobSearchSelect
|
||||
convertedOnly={!bodyshop.tt_allow_post_to_invoiced}
|
||||
notExported={!bodyshop.tt_allow_post_to_invoiced}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
||||
@@ -35,6 +35,24 @@ export const updateCurrentUser = (userDetails) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const updateCurrentPassword = async (password) => {
|
||||
const currentUser = await getCurrentUser();
|
||||
|
||||
return currentUser.updatePassword(password);
|
||||
|
||||
// return new Promise((resolve, reject) => {
|
||||
// const unsubscribe = auth.onAuthStateChanged(
|
||||
// (userAuth) => {
|
||||
// userAuth.updatePassword(password).then((r) => {
|
||||
// unsubscribe();
|
||||
// resolve(userAuth);
|
||||
// });
|
||||
// },
|
||||
// (error) => reject(error)
|
||||
// );
|
||||
// });
|
||||
};
|
||||
|
||||
let messaging;
|
||||
try {
|
||||
messaging = firebase.messaging();
|
||||
|
||||
@@ -24,6 +24,10 @@ export const QUERY_JOBS_FOR_EXPORT = gql`
|
||||
clm_total
|
||||
clm_no
|
||||
ins_co_nm
|
||||
exportlogs {
|
||||
id
|
||||
successful
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -37,6 +41,10 @@ export const QUERY_BILLS_FOR_EXPORT = gql`
|
||||
invoice_number
|
||||
is_credit_memo
|
||||
total
|
||||
exportlogs {
|
||||
id
|
||||
successful
|
||||
}
|
||||
job {
|
||||
id
|
||||
ro_number
|
||||
@@ -73,6 +81,10 @@ export const QUERY_PAYMENTS_FOR_EXPORT = gql`
|
||||
transactionid
|
||||
paymentnum
|
||||
date
|
||||
exportlogs {
|
||||
id
|
||||
successful
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -20,6 +20,7 @@ export const QUERY_ALL_ACTIVE_APPOINTMENTS = gql`
|
||||
isintake
|
||||
block
|
||||
color
|
||||
note
|
||||
job {
|
||||
alt_transport
|
||||
ro_number
|
||||
@@ -69,6 +70,7 @@ export const INSERT_APPOINTMENT_BLOCK = gql`
|
||||
title
|
||||
isintake
|
||||
block
|
||||
note
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,6 +92,7 @@ export const INSERT_APPOINTMENT = gql`
|
||||
isintake
|
||||
block
|
||||
color
|
||||
note
|
||||
}
|
||||
}
|
||||
update_jobs(
|
||||
@@ -116,6 +119,7 @@ export const QUERY_APPOINTMENT_BY_DATE = gql`
|
||||
isintake
|
||||
block
|
||||
color
|
||||
note
|
||||
job {
|
||||
alt_transport
|
||||
ro_number
|
||||
@@ -168,6 +172,7 @@ export const UPDATE_APPOINTMENT = gql`
|
||||
isintake
|
||||
block
|
||||
color
|
||||
note
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,6 +203,7 @@ export const QUERY_APPOINTMENTS_BY_JOBID = gql`
|
||||
canceled
|
||||
created_at
|
||||
block
|
||||
note
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
import { gql } from "@apollo/client";
|
||||
|
||||
export const QUERY_AUDIT_TRAIL = gql`
|
||||
query QUERY_AUDIT_TRAIL($id: uuid!) {
|
||||
audit_trail(where: { recordid: { _eq: $id } }) {
|
||||
query QUERY_AUDIT_TRAIL($jobid: uuid!) {
|
||||
audit_trail(
|
||||
where: { jobid: { _eq: $jobid } }
|
||||
order_by: { created: desc }
|
||||
) {
|
||||
useremail
|
||||
tabname
|
||||
schemaname
|
||||
recordid
|
||||
jobid
|
||||
operation
|
||||
old_val
|
||||
new_val
|
||||
id
|
||||
created
|
||||
bodyshopid
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const INSERT_AUDIT_TRAIL = gql`
|
||||
mutation INSERT_AUDIT_TRAIL($auditObj: audit_trail_insert_input!) {
|
||||
insert_audit_trail_one(object: $auditObj) {
|
||||
id
|
||||
jobid
|
||||
billid
|
||||
bodyshopid
|
||||
created
|
||||
operation
|
||||
useremail
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -92,6 +92,8 @@ export const QUERY_BODYSHOP = gql`
|
||||
cdk_dealerid
|
||||
features
|
||||
attach_pdf_to_email
|
||||
tt_allow_post_to_invoiced
|
||||
cdk_configuration
|
||||
employees {
|
||||
id
|
||||
active
|
||||
@@ -180,6 +182,8 @@ export const UPDATE_SHOP = gql`
|
||||
md_jobline_presets
|
||||
cdk_dealerid
|
||||
attach_pdf_to_email
|
||||
tt_allow_post_to_invoiced
|
||||
cdk_configuration
|
||||
employees {
|
||||
id
|
||||
first_name
|
||||
@@ -206,6 +210,10 @@ export const QUERY_INTAKE_CHECKLIST = gql`
|
||||
scheduled_delivery
|
||||
intakechecklist
|
||||
status
|
||||
owner {
|
||||
allow_text_message
|
||||
id
|
||||
}
|
||||
labhrs: joblines_aggregate(
|
||||
where: {
|
||||
_and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }]
|
||||
|
||||
@@ -1025,6 +1025,7 @@ export const SEARCH_JOBS_FOR_AUTOCOMPLETE = gql`
|
||||
$search: String
|
||||
$isConverted: Boolean
|
||||
$notExported: Boolean
|
||||
$notInvoiced: Boolean
|
||||
) {
|
||||
search_jobs(
|
||||
args: { search: $search }
|
||||
@@ -1033,6 +1034,7 @@ export const SEARCH_JOBS_FOR_AUTOCOMPLETE = gql`
|
||||
_and: {
|
||||
converted: { _eq: $isConverted }
|
||||
date_exported: { _is_null: $notExported }
|
||||
date_invoiced: { _is_null: $notInvoiced }
|
||||
}
|
||||
}
|
||||
) {
|
||||
@@ -1847,6 +1849,10 @@ export const QUERY_JOB_CHECKLISTS = gql`
|
||||
scheduled_delivery
|
||||
actual_delivery
|
||||
production_vars
|
||||
owner {
|
||||
id
|
||||
allow_text_message
|
||||
}
|
||||
bodyshop {
|
||||
id
|
||||
intakechecklist
|
||||
@@ -1869,3 +1875,100 @@ export const FIND_JOBS_BY_CLAIM = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_JOB_EXPORT_DMS = gql`
|
||||
query QUERY_JOB_CLOSE_DETAILS($id: uuid!) {
|
||||
jobs_by_pk(id: $id) {
|
||||
ro_number
|
||||
invoice_allocation
|
||||
ins_co_id
|
||||
id
|
||||
ded_amt
|
||||
ded_status
|
||||
depreciation_taxes
|
||||
other_amount_payable
|
||||
towing_payable
|
||||
storage_payable
|
||||
adjustment_bottom_line
|
||||
federal_tax_rate
|
||||
state_tax_rate
|
||||
local_tax_rate
|
||||
tax_tow_rt
|
||||
tax_str_rt
|
||||
tax_paint_mat_rt
|
||||
tax_sub_rt
|
||||
tax_lbr_rt
|
||||
tax_levies_rt
|
||||
parts_tax_rates
|
||||
job_totals
|
||||
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_mapa
|
||||
rate_mash
|
||||
rate_matd
|
||||
status
|
||||
date_exported
|
||||
date_invoiced
|
||||
voided
|
||||
scheduled_completion
|
||||
actual_completion
|
||||
scheduled_delivery
|
||||
actual_delivery
|
||||
scheduled_in
|
||||
actual_in
|
||||
bills {
|
||||
id
|
||||
federal_tax_rate
|
||||
local_tax_rate
|
||||
state_tax_rate
|
||||
is_credit_memo
|
||||
billlines {
|
||||
actual_cost
|
||||
cost_center
|
||||
id
|
||||
quantity
|
||||
}
|
||||
}
|
||||
joblines(where: { removed: { _eq: false } }) {
|
||||
id
|
||||
removed
|
||||
tax_part
|
||||
line_desc
|
||||
prt_dsmk_p
|
||||
prt_dsmk_m
|
||||
part_type
|
||||
oem_partno
|
||||
db_price
|
||||
act_price
|
||||
part_qty
|
||||
mod_lbr_ty
|
||||
db_hrs
|
||||
mod_lb_hrs
|
||||
lbr_op
|
||||
lbr_amt
|
||||
op_code_desc
|
||||
profitcenter_labor
|
||||
profitcenter_part
|
||||
prt_dsmk_p
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -5,6 +5,7 @@ export const INSERT_NEW_PARTS_ORDERS = gql`
|
||||
insert_parts_orders(objects: $po) {
|
||||
returning {
|
||||
id
|
||||
order_number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ import { gql } from "@apollo/client";
|
||||
|
||||
export const SUBSCRIPTION_SCOREBOARD = gql`
|
||||
subscription SUBSCRIPTION_SCOREBOARD($start: date!, $end: date!) {
|
||||
scoreboard(where: { _and: { date: { _gte: $start, _lte: $end } } }) {
|
||||
scoreboard(
|
||||
where: { _and: { date: { _gte: $start, _lte: $end } } }
|
||||
order_by: { date: asc }
|
||||
) {
|
||||
id
|
||||
painthrs
|
||||
bodyhrs
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
import { Result, Timeline, Space, Tag, Divider, Button } from "antd";
|
||||
//import { useQuery } from "@apollo/client";
|
||||
import { Button, Col, Result, Row, Select, Space } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import SocketIO from "socket.io-client";
|
||||
//import AlertComponent from "../../components/alert/alert.component";
|
||||
import DmsAllocationsSummary from "../../components/dms-allocations-summary/dms-allocations-summary.component";
|
||||
import DmsCustomerSelector from "../../components/dms-customer-selector/dms-customer-selector.component";
|
||||
import DmsLogEvents from "../../components/dms-log-events/dms-log-events.component";
|
||||
import DmsPostForm from "../../components/dms-post-form/dms-post-form.component";
|
||||
//import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||
import { auth } from "../../firebase/firebase.utils";
|
||||
//import { QUERY_JOB_EXPORT_DMS } from "../../graphql/jobs.queries";
|
||||
import {
|
||||
setBreadcrumbs,
|
||||
setSelectedHeader,
|
||||
} from "../../redux/application/application.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import SocketIO from "socket.io-client";
|
||||
import { auth } from "../../firebase/firebase.utils";
|
||||
import moment from "moment";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -38,7 +47,15 @@ export const socket = SocketIO(
|
||||
|
||||
export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
|
||||
const { t } = useTranslation();
|
||||
const [logLevel, setLogLevel] = useState("TRACE");
|
||||
const [logs, setLogs] = useState([]);
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const { jobId } = search;
|
||||
|
||||
// const { loading, error } = useQuery(QUERY_JOB_EXPORT_DMS, {
|
||||
// variables: { id: jobId },
|
||||
// skip: true, //!jobId,
|
||||
// });
|
||||
|
||||
useEffect(() => {
|
||||
document.title = t("titles.dms");
|
||||
@@ -55,6 +72,19 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
|
||||
socket.on("connected", () => {
|
||||
console.log("Connected again.");
|
||||
});
|
||||
socket.on("reconnect", () => {
|
||||
console.log("Connected again.");
|
||||
setLogs((logs) => {
|
||||
return [
|
||||
...logs,
|
||||
{
|
||||
timestamp: new Date(),
|
||||
level: "WARNING",
|
||||
message: "Reconnected to CDK Export Service",
|
||||
},
|
||||
];
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("log-event", (payload) => {
|
||||
setLogs((logs) => {
|
||||
@@ -63,74 +93,68 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
|
||||
});
|
||||
|
||||
socket.connect();
|
||||
socket.emit("set-log-level", "TRACE");
|
||||
socket.emit("set-log-level", logLevel);
|
||||
|
||||
return () => {
|
||||
socket.removeAllListeners();
|
||||
socket.disconnect();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
if (!bodyshop.cdk_dealerid) return <Result status="404" />;
|
||||
if (!jobId || !bodyshop.cdk_dealerid) return <Result status="404" />;
|
||||
|
||||
const dmsType = determineDmsType(bodyshop);
|
||||
|
||||
// if (loading) return <LoadingSpinner />;
|
||||
// if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
socket.emit(
|
||||
`${dmsType}-export-job`,
|
||||
"752a4f5f-22ab-414b-b182-98d4e62227ef"
|
||||
);
|
||||
}}
|
||||
>
|
||||
Export
|
||||
</Button>
|
||||
<Space>
|
||||
<Button
|
||||
onClick={() => {
|
||||
socket.emit(
|
||||
`${dmsType}-export-job`,
|
||||
"752a4f5f-22ab-414b-b182-98d4e62227ef"
|
||||
);
|
||||
}}
|
||||
>
|
||||
Export
|
||||
</Button>
|
||||
<Select
|
||||
placeholder="Log Level"
|
||||
value={logLevel}
|
||||
onChange={(value) => {
|
||||
setLogLevel(value);
|
||||
socket.emit("set-log-level", value);
|
||||
}}
|
||||
>
|
||||
<Select.Option key="TRACE">TRACE</Select.Option>
|
||||
<Select.Option key="DEBUG">DEBUG</Select.Option>
|
||||
<Select.Option key="INFO">INFO</Select.Option>
|
||||
<Select.Option key="WARNING">WARNING</Select.Option>
|
||||
<Select.Option key="ERROR">ERROR</Select.Option>
|
||||
</Select>
|
||||
<Button onClick={() => setLogs([])}>Clear Logs</Button>
|
||||
</Space>
|
||||
<Row gutter={32}>
|
||||
<Col span={18}>
|
||||
<DmsAllocationsSummary socket={socket} jobId={jobId} />
|
||||
<DmsPostForm socket={socket} jobId={jobId} />
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<div style={{ maxHeight: "500px", overflowY: "auto" }}>
|
||||
<DmsLogEvents socket={socket} logs={logs} />
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Button
|
||||
onClick={() => {
|
||||
setLogs([]);
|
||||
socket.disconnect();
|
||||
socket.connect();
|
||||
}}
|
||||
>
|
||||
reconnect
|
||||
</Button>
|
||||
|
||||
<Timeline pending={socket.connected && "Processing..."} reverse={true}>
|
||||
{logs.map((log, idx) => (
|
||||
<Timeline.Item key={idx} color={LogLevelHierarchy(log.level)}>
|
||||
<Space wrap align="start" style={{}}>
|
||||
<Tag color={LogLevelHierarchy(log.level)}>{log.level}</Tag>
|
||||
<span>{moment(log.timestamp).format("MM/DD/YYYY HH:MM:ss")}</span>
|
||||
<Divider type="vertical" />
|
||||
<span>{log.message}</span>
|
||||
</Space>
|
||||
</Timeline.Item>
|
||||
))}
|
||||
</Timeline>
|
||||
<DmsCustomerSelector />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LogLevelHierarchy(level) {
|
||||
switch (level) {
|
||||
case "TRACE":
|
||||
return "pink";
|
||||
case "DEBUG":
|
||||
return "orange";
|
||||
case "INFO":
|
||||
return "blue";
|
||||
case "WARNING":
|
||||
return "yellow";
|
||||
case "ERROR":
|
||||
return "red";
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const determineDmsType = (bodyshop) => {
|
||||
if (bodyshop.cdk_dealerid) return "cdk";
|
||||
else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Card, Col, Result, Row, Space } from "antd";
|
||||
import { Card, Col, Result, Row, Space, Typography } from "antd";
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -84,6 +84,9 @@ export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader }) {
|
||||
|
||||
return (
|
||||
<RbacWrapper action="jobs:admin">
|
||||
<Typography.Title level={4} style={{ color: "tomato" }}>
|
||||
{t("jobs.labels.adminwarning")}
|
||||
</Typography.Title>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col {...colSpan}>
|
||||
<Card style={cardStyle}>
|
||||
|
||||
@@ -5,6 +5,7 @@ import Icon, {
|
||||
FileImageFilled,
|
||||
PrinterFilled,
|
||||
ToolFilled,
|
||||
HistoryOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import {
|
||||
Button,
|
||||
@@ -45,6 +46,9 @@ import ScheduleJobModalContainer from "../../components/schedule-job-modal/sched
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import JobAuditTrail from "../../components/job-audit-trail/job-audit-trail.component";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -53,6 +57,8 @@ const mapStateToProps = createStructuredSelector({
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setPrintCenterContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "printCenter" })),
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
});
|
||||
export function JobsDetailPage({
|
||||
setPrintCenterContext,
|
||||
@@ -60,6 +66,7 @@ export function JobsDetailPage({
|
||||
job,
|
||||
mutationUpdateJob,
|
||||
handleSubmit,
|
||||
insertAuditTrail,
|
||||
refetch,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
@@ -81,6 +88,7 @@ export function JobsDetailPage({
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
setLoading(true);
|
||||
|
||||
const result = await mutationUpdateJob({
|
||||
variables: {
|
||||
jobId: job.id,
|
||||
@@ -105,6 +113,62 @@ export function JobsDetailPage({
|
||||
notification["success"]({
|
||||
message: t("jobs.successes.savetitle"),
|
||||
});
|
||||
const changedAuditFields = form.getFieldsValue(
|
||||
[
|
||||
"scheduled_in",
|
||||
"actual_in",
|
||||
"scheduled_completion",
|
||||
"actual_completion",
|
||||
"scheduled_delivery",
|
||||
"actual_delivery",
|
||||
"date_invoiced",
|
||||
"ins_co_nm",
|
||||
"ded_amt",
|
||||
"ded_status",
|
||||
"date_exported",
|
||||
"special_coverage_policy",
|
||||
"ca_gst_registrant",
|
||||
"ca_bc_pvrt",
|
||||
"scheduled_in",
|
||||
"rate_la1",
|
||||
"rate_la2",
|
||||
"rate_la3",
|
||||
"rate_la4",
|
||||
"rate_laa",
|
||||
"rate_lab",
|
||||
"rate_lad",
|
||||
"rate_lae",
|
||||
"rate_laf",
|
||||
"rate_lag",
|
||||
"rate_lam",
|
||||
"rate_lar",
|
||||
"rate_las",
|
||||
"rate_lau",
|
||||
"rate_ma2s",
|
||||
"rate_ma2t",
|
||||
"rate_ma3s",
|
||||
"rate_mabl",
|
||||
"rate_macs",
|
||||
"rate_mapa",
|
||||
"rate_mahw",
|
||||
"rate_mash",
|
||||
"rate_matd",
|
||||
],
|
||||
(meta) => meta && meta.touched
|
||||
);
|
||||
|
||||
Object.keys(changedAuditFields).forEach((key) => {
|
||||
insertAuditTrail({
|
||||
jobid: job.id,
|
||||
operation: AuditTrailMapping.jobfieldchange(
|
||||
key,
|
||||
changedAuditFields[key] instanceof moment
|
||||
? moment(changedAuditFields[key]).format("MM/DD/YYYY hh:mm a")
|
||||
: changedAuditFields[key]
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
await refetch();
|
||||
form.setFieldsValue(transormJobToForm(job));
|
||||
form.resetFields();
|
||||
@@ -279,6 +343,17 @@ export function JobsDetailPage({
|
||||
>
|
||||
<JobNotesContainer jobId={job.id} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<HistoryOutlined />
|
||||
{t("jobs.labels.audit")}
|
||||
</span>
|
||||
}
|
||||
key="audit"
|
||||
>
|
||||
<JobAuditTrail jobId={job.id} />
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Form>
|
||||
</div>
|
||||
|
||||
@@ -53,3 +53,8 @@ export const setOnline = (isOnline) => ({
|
||||
type: ApplicationActionTypes.SET_ONLINE_STATUS,
|
||||
payload: isOnline,
|
||||
});
|
||||
|
||||
export const insertAuditTrail = ({ jobid, billid, operation }) => ({
|
||||
type: ApplicationActionTypes.INSERT_AUDIT_TRAIL,
|
||||
payload: { jobid, billid, operation },
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import moment from "moment";
|
||||
import { all, call, put, select, takeLatest } from "redux-saga/effects";
|
||||
import { QUERY_SCHEDULE_LOAD_DATA } from "../../graphql/appointments.queries";
|
||||
import { INSERT_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
|
||||
import client from "../../utils/GraphQLClient";
|
||||
import { CalculateLoad, CheckJobBucket } from "../../utils/SSSUtils";
|
||||
import {
|
||||
@@ -125,6 +126,56 @@ export function* calculateScheduleLoad({ payload: end }) {
|
||||
}
|
||||
}
|
||||
|
||||
export function* applicationSagas() {
|
||||
yield all([call(onCalculateScheduleLoad)]);
|
||||
export function* onInsertAuditTrail() {
|
||||
yield takeLatest(
|
||||
ApplicationActionTypes.INSERT_AUDIT_TRAIL,
|
||||
insertAuditTrailSaga
|
||||
);
|
||||
}
|
||||
|
||||
export function* insertAuditTrailSaga({
|
||||
payload: { jobid, billid, operation },
|
||||
}) {
|
||||
const state = yield select();
|
||||
const bodyshop = state.user.bodyshop;
|
||||
const currentUser = state.user.currentUser;
|
||||
console.log(
|
||||
"Inserting audit trail for",
|
||||
bodyshop.shopname,
|
||||
currentUser.email,
|
||||
jobid,
|
||||
billid,
|
||||
operation
|
||||
);
|
||||
const variables = {
|
||||
auditObj: {
|
||||
bodyshopid: bodyshop.id,
|
||||
jobid,
|
||||
billid,
|
||||
operation,
|
||||
useremail: currentUser.email,
|
||||
},
|
||||
};
|
||||
yield client.mutate({
|
||||
mutation: INSERT_AUDIT_TRAIL,
|
||||
variables,
|
||||
update(cache, { data }) {
|
||||
cache.modify({
|
||||
fields: {
|
||||
audit_trail(existingAuditTrail, { readField }) {
|
||||
const newAuditTrail = cache.writeQuery({
|
||||
data: data.insert_audit_trail_one,
|
||||
query: INSERT_AUDIT_TRAIL,
|
||||
variables,
|
||||
});
|
||||
return [...existingAuditTrail, newAuditTrail];
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function* applicationSagas() {
|
||||
yield all([call(onCalculateScheduleLoad), call(onInsertAuditTrail)]);
|
||||
}
|
||||
|
||||
@@ -10,5 +10,6 @@ const ApplicationActionTypes = {
|
||||
SET_JOB_READONLY: "SET_JOB_READONLY",
|
||||
SET_PARTNER_VERSION: "SET_PARTNER_VERSION",
|
||||
SET_ONLINE_STATUS: "SET_ONLINE_STATUS",
|
||||
INSERT_AUDIT_TRAIL: "INSERT_AUDIT_TRAIL",
|
||||
};
|
||||
export default ApplicationActionTypes;
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import DmsActions from "./dms.types";
|
||||
|
||||
export const endLoading = (options) => ({
|
||||
// type: DmsActions.END_LOADING,
|
||||
payload: options,
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
import DmsActionTypes from "./dms.types";
|
||||
|
||||
const INITIAL_STATE = {
|
||||
eventLog: [],
|
||||
};
|
||||
|
||||
const dmsReducer = (state = INITIAL_STATE, action) => {
|
||||
switch (action.type) {
|
||||
// case ApplicationActionTypes.SET_SELECTED_HEADER:
|
||||
// return {
|
||||
// ...state,
|
||||
// selectedHeader: action.payload,
|
||||
// };
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default dmsReducer;
|
||||
@@ -1,14 +0,0 @@
|
||||
import { all, call } from "redux-saga/effects";
|
||||
//import DmsActionTypes from "./dms.types";
|
||||
|
||||
export function* onCalculateScheduleLoad() {
|
||||
// yield takeLatest(
|
||||
// DmsActionTypes.CALCULATE_SCHEDULE_LOAD,
|
||||
// calculateScheduleLoad
|
||||
// );
|
||||
}
|
||||
export function* calculateScheduleLoad({ payload: end }) {}
|
||||
|
||||
export function* dmsSagas() {
|
||||
yield all([call()]);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { createSelector } from "reselect";
|
||||
|
||||
const selectDms = (state) => state.dms;
|
||||
|
||||
export const selectEventLog = createSelector(
|
||||
[selectDms],
|
||||
(dms) => dms.eventLog
|
||||
);
|
||||
@@ -1,4 +0,0 @@
|
||||
const DmsActionTypes = {
|
||||
ADD_EVENT: "ADD_EVENT",
|
||||
};
|
||||
export default DmsActionTypes;
|
||||
@@ -39,7 +39,7 @@ export function* openChatByPhone({ payload }) {
|
||||
data: { conversations },
|
||||
} = yield client.query({
|
||||
query: CONVERSATION_ID_BY_PHONE,
|
||||
variables: { phone: phone(phone_num)[0] },
|
||||
variables: { phone: phone(phone_num).phoneNumber },
|
||||
fetchPolicy: "network-only",
|
||||
});
|
||||
|
||||
@@ -53,7 +53,7 @@ export function* openChatByPhone({ payload }) {
|
||||
variables: {
|
||||
conversation: [
|
||||
{
|
||||
phone_num: phone(phone_num)[0],
|
||||
phone_num: phone(phone_num).phoneNumber,
|
||||
bodyshopid: bodyshop.id,
|
||||
job_conversations: jobid ? { data: { jobid: jobid } } : null,
|
||||
},
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
validatePasswordResetSuccess,
|
||||
} from "./user.actions";
|
||||
import UserActionTypes from "./user.types";
|
||||
import * as Sentry from "@sentry/browser";
|
||||
|
||||
export function* onEmailSignInStart() {
|
||||
yield takeLatest(UserActionTypes.EMAIL_SIGN_IN_START, signInWithEmail);
|
||||
@@ -100,8 +101,12 @@ export function* onUpdateUserDetails() {
|
||||
}
|
||||
export function* updateUserDetails(userDetails) {
|
||||
try {
|
||||
yield updateCurrentUser(userDetails.payload);
|
||||
yield put(updateUserDetailsSuccess(userDetails.payload));
|
||||
const updatedDetails = yield updateCurrentUser(userDetails.payload);
|
||||
console.log(
|
||||
"🚀 ~ file: user.sagas.js ~ line 104 ~ updatedDetails",
|
||||
updatedDetails
|
||||
);
|
||||
yield put(updateUserDetailsSuccess(updatedDetails));
|
||||
notification.open({
|
||||
type: "success",
|
||||
message: i18next.t("profile.successes.updated"),
|
||||
@@ -174,6 +179,11 @@ export function* signInSuccessSaga({ payload }) {
|
||||
]);
|
||||
console.log("Setting $crisp segments", ["user"]);
|
||||
window.$crisp.push(["set", "session:segments", [["user"]]]);
|
||||
|
||||
Sentry.setUser({
|
||||
email: payload.email,
|
||||
username: payload.displayName || payload.email,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error updating Crisp settings.", error);
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"fields": {
|
||||
"alt_transport": "Alt. Trans.",
|
||||
"color": "Appointment Color",
|
||||
"note": "Appt. Note",
|
||||
"time": "Appointment Time",
|
||||
"title": "Title"
|
||||
},
|
||||
@@ -52,6 +53,7 @@
|
||||
"nocompletingjobs": "No jobs scheduled for completion.",
|
||||
"nodateselected": "No date has been selected.",
|
||||
"priorappointments": "Previous Appointments",
|
||||
"reminder": "This is {{shopname}} reminding you about an appointment on {{date}} at {{time}}. Please let us know if you are not able to make the appointment. We look forward to seeing you soon. ",
|
||||
"scheduledfor": "Scheduled appointment for: ",
|
||||
"smartscheduling": "Smart Scheduling",
|
||||
"suggesteddates": "Suggested Dates"
|
||||
@@ -82,6 +84,24 @@
|
||||
"values": "Values"
|
||||
}
|
||||
},
|
||||
"audit_trail": {
|
||||
"messages": {
|
||||
"billposted": "Bill with invoice number {{invoice_number}} posted.",
|
||||
"billupdated": "Bill with invoice number {{invoice_number}} updated.",
|
||||
"jobassignmentchange": "Employee {{name}} assigned to {{operation}}",
|
||||
"jobassignmentremoved": "Employee assignment removed for {{operation}}",
|
||||
"jobchecklist": "Checklist type \"{{type}}\" completed. In production set to {{inproduction}}. Status set to {{status}}.",
|
||||
"jobconverted": "Job converted and assigned number {{ro_number}}.",
|
||||
"jobfieldchanged": "Job field $t(jobs.fields.{{field}}) changed to {{value}}.",
|
||||
"jobimported": "Job imported.",
|
||||
"jobinproductionchange": "Job production status set to {{inproduction}}",
|
||||
"jobmodifylbradj": "Labor adjustments modified.",
|
||||
"jobspartsorder": "Parts order {{order_number}} added to job.",
|
||||
"jobspartsreturn": "Parts return {{order_number}} added to job.",
|
||||
"jobstatuschange": "Job status changed to {{status}}.",
|
||||
"jobsupplement": "Job supplement imported."
|
||||
}
|
||||
},
|
||||
"billlines": {
|
||||
"actions": {
|
||||
"newline": "New Line"
|
||||
@@ -211,6 +231,12 @@
|
||||
"deliver": {
|
||||
"templates": "Delivery Templates"
|
||||
},
|
||||
"dms": {
|
||||
"default_journal": "Default Journal",
|
||||
"dms_acctnumber": "DMS Account #",
|
||||
"dms_wip_acctnumber": "DMS W.I.P. Account #",
|
||||
"mappingname": "DMS Mapping Name"
|
||||
},
|
||||
"email": "General Shop Email",
|
||||
"enforce_class": "Enforce Class on Conversion?",
|
||||
"enforce_referral": "Enforce Referrals",
|
||||
@@ -426,6 +452,7 @@
|
||||
"production_statuses": "Production Statuses"
|
||||
},
|
||||
"target_touchtime": "Target Touch Time",
|
||||
"tt_allow_post_to_invoiced": "Allow Time Tickets to be posted to Invoiced & Exported Jobs",
|
||||
"use_fippa": "Use FIPPA for Names on Generated Documents?",
|
||||
"website": "Website",
|
||||
"zip_post": "Zip/Postal Code"
|
||||
@@ -446,6 +473,10 @@
|
||||
"defaultcostsmapping": "Default Costs Mapping",
|
||||
"defaultprofitsmapping": "Default Profits Mapping",
|
||||
"deliverchecklist": "Delivery Checklist",
|
||||
"dms": {
|
||||
"cdk_dealerid": "CDK Dealer ID",
|
||||
"title": "DMS"
|
||||
},
|
||||
"employees": "Employees",
|
||||
"insurancecos": "Insurance Companies",
|
||||
"intakechecklist": "Intake Checklist",
|
||||
@@ -491,6 +522,7 @@
|
||||
},
|
||||
"labels": {
|
||||
"addtoproduction": "Add Job to Production?",
|
||||
"allow_text_message": "Permission to Text?",
|
||||
"checklist": "Checklist",
|
||||
"printpack": "Job Intake Print Pack",
|
||||
"removefromproduction": "Remove job from production?"
|
||||
@@ -793,6 +825,9 @@
|
||||
"exportlogs": {
|
||||
"fields": {
|
||||
"createdat": "Created At"
|
||||
},
|
||||
"labels": {
|
||||
"attempts": "Export Attempts"
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
@@ -857,6 +892,7 @@
|
||||
"message": "Message",
|
||||
"monday": "Monday",
|
||||
"na": "N/A",
|
||||
"newpassword": "New Password",
|
||||
"no": "No",
|
||||
"nointernet": "It looks like you're not connected to the internet.",
|
||||
"nointernet_sub": "Please check your connection and try again. ",
|
||||
@@ -877,6 +913,7 @@
|
||||
"sendagain": "Send Again",
|
||||
"sendby": "Send By",
|
||||
"signin": "Sign In",
|
||||
"sms": "SMS",
|
||||
"sub_status": {
|
||||
"expired": "The subscription for this shop has expired. Please contact technical support to reactivate the subscription. "
|
||||
},
|
||||
@@ -1027,6 +1064,7 @@
|
||||
"changestatus": "Change Status",
|
||||
"convert": "Convert",
|
||||
"deliver": "Deliver",
|
||||
"dmsautoallocate": "DMS Auto Allocate",
|
||||
"export": "Export",
|
||||
"exportcustdata": "Export Customer Data",
|
||||
"exportselected": "Export Selected",
|
||||
@@ -1036,6 +1074,7 @@
|
||||
"intake": "Intake",
|
||||
"manualnew": "Create New Job Manually",
|
||||
"mark": "Mark",
|
||||
"markasexported": "Mark as Exported",
|
||||
"markpstexempt": "Mark Job PST Exempt",
|
||||
"markpstexemptconfirm": "Are you sure you want to do this? To undo this, you must manually update all PST rates.",
|
||||
"postbills": "Post Bills",
|
||||
@@ -1046,6 +1085,7 @@
|
||||
"schedule": "Schedule",
|
||||
"sendcsi": "Send CSI",
|
||||
"sync": "Sync",
|
||||
"uninvoice": "Uninvoice",
|
||||
"unvoid": "Unvoid Job",
|
||||
"viewchecklist": "View Checklists",
|
||||
"viewdetail": "View Details"
|
||||
@@ -1104,6 +1144,14 @@
|
||||
"ded_amt": "Deductible",
|
||||
"ded_status": "Deductible Status",
|
||||
"depreciation_taxes": "Depreciation/Taxes",
|
||||
"dms": {
|
||||
"center": "Center",
|
||||
"cost": "Cost",
|
||||
"cost_dms_acctnumber": "Cost DMS Acct #",
|
||||
"dms_wip_acctnumber": "Cost WIP DMS Acct #",
|
||||
"sale": "Sale",
|
||||
"sale_dms_acctnumber": "Sale DMS Acct #"
|
||||
},
|
||||
"driveable": "Driveable",
|
||||
"employee_body": "Body",
|
||||
"employee_csr": "Customer Service Rep.",
|
||||
@@ -1227,6 +1275,7 @@
|
||||
"servicecar": "Service Car",
|
||||
"servicing_dealer": "Servicing Dealer",
|
||||
"servicing_dealer_contact": "Servicing Dealer Contact",
|
||||
"special_coverage_policy": "Special Coverage Policy",
|
||||
"specialcoveragepolicy": "Special Coverage Policy",
|
||||
"state_tax_rate": "Provincial/State Tax Rate",
|
||||
"status": "Job Status",
|
||||
@@ -1264,6 +1313,7 @@
|
||||
"additionaltotal": "Additional Total",
|
||||
"adjustmentrate": "Adjustment Rate",
|
||||
"adjustments": "Adjustments",
|
||||
"adminwarning": "Use the functionality on this page at your own risk. You are responsible for any and all changes to your data.",
|
||||
"allocations": "Allocations",
|
||||
"alreadyclosed": "This job has already been closed.",
|
||||
"appointmentconfirmation": "Send confirmation to customer?",
|
||||
@@ -1316,7 +1366,8 @@
|
||||
"waived": "Waived"
|
||||
},
|
||||
"deleteconfirm": "Are you sure you want to delete this job? This cannot be undone. ",
|
||||
"deleteintake": "Delete Intake",
|
||||
"deletedelivery": "Delete Delivery Checklist",
|
||||
"deleteintake": "Delete Intake Checklist",
|
||||
"deliverchecklist": "Deliver Checklist",
|
||||
"difference": "Difference",
|
||||
"diskscan": "Scan Disk for Estimates",
|
||||
@@ -1953,12 +2004,15 @@
|
||||
"bills": "Bills",
|
||||
"exportlogs": "Export Logs",
|
||||
"jobs": "Jobs",
|
||||
"parts_orders": "Parts Orders",
|
||||
"payments": "Payments",
|
||||
"scoreboard": "Scoreboard",
|
||||
"timetickets": "Timetickets"
|
||||
},
|
||||
"vendor": "Vendor"
|
||||
},
|
||||
"templates": {
|
||||
"anticipated_revenue": "Anticipated Revenue",
|
||||
"attendance_detail": "Attendance (All Employees)",
|
||||
"attendance_employee": "Employee Attendance",
|
||||
"attendance_summary": "Attendance Summary (All Employees)",
|
||||
@@ -1994,11 +2048,13 @@
|
||||
"job_costing_ro_date_summary": "Job Costing by RO - Summary",
|
||||
"job_costing_ro_estimator": "Job Costing by Estimator",
|
||||
"job_costing_ro_ins_co": "Job Costing by RO Source",
|
||||
"lag_time": "Lag Time",
|
||||
"open_orders": "Open Orders by Date",
|
||||
"open_orders_csr": "Open Orders by CSR",
|
||||
"open_orders_estimator": "Open Orders by Estimator",
|
||||
"open_orders_ins_co": "Open Orders by Insurance Company",
|
||||
"parts_backorder": "Backordered Parts",
|
||||
"parts_not_recieved": "Parts Not Received",
|
||||
"payments_by_date": "Payments by Date",
|
||||
"payments_by_date_type": "Payments by Date and Type",
|
||||
"production_by_csr": "Production by CSR",
|
||||
@@ -2015,6 +2071,8 @@
|
||||
"purchases_grouped_by_vendor_detailed": "Purchases Grouped by Vendor - Detailed",
|
||||
"purchases_grouped_by_vendor_summary": "Purchases Grouped by Vendor - Summary",
|
||||
"schedule": "Appointment Schedule",
|
||||
"scoreboard_detail": "Scoreboard Detail",
|
||||
"scoreboard_summary": "Scoreboard Summary",
|
||||
"supplement_ratio_ins_co": "Supplement Ratio by Source",
|
||||
"thank_you_date": "Thank You Letters",
|
||||
"timetickets": "Time Tickets",
|
||||
@@ -2054,6 +2112,11 @@
|
||||
"updated": "Scoreboard updated."
|
||||
}
|
||||
},
|
||||
"scoredboard": {
|
||||
"successes": {
|
||||
"updated": "Scoreboard entry updated."
|
||||
}
|
||||
},
|
||||
"tech": {
|
||||
"fields": {
|
||||
"employeeid": "Employee ID",
|
||||
@@ -2221,6 +2284,7 @@
|
||||
},
|
||||
"user": {
|
||||
"actions": {
|
||||
"changepassword": "Change Password",
|
||||
"signout": "Sign Out",
|
||||
"updateprofile": "Update Profile"
|
||||
},
|
||||
@@ -2235,6 +2299,9 @@
|
||||
},
|
||||
"labels": {
|
||||
"actions": "Actions"
|
||||
},
|
||||
"successess": {
|
||||
"passwordchanged": "Password changed successfully. "
|
||||
}
|
||||
},
|
||||
"vehicles": {
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"fields": {
|
||||
"alt_transport": "",
|
||||
"color": "",
|
||||
"note": "",
|
||||
"time": "",
|
||||
"title": "Título"
|
||||
},
|
||||
@@ -52,6 +53,7 @@
|
||||
"nocompletingjobs": "",
|
||||
"nodateselected": "No se ha seleccionado ninguna fecha.",
|
||||
"priorappointments": "Nombramientos previos",
|
||||
"reminder": "",
|
||||
"scheduledfor": "Cita programada para:",
|
||||
"smartscheduling": "",
|
||||
"suggesteddates": ""
|
||||
@@ -82,6 +84,24 @@
|
||||
"values": ""
|
||||
}
|
||||
},
|
||||
"audit_trail": {
|
||||
"messages": {
|
||||
"billposted": "",
|
||||
"billupdated": "",
|
||||
"jobassignmentchange": "",
|
||||
"jobassignmentremoved": "",
|
||||
"jobchecklist": "",
|
||||
"jobconverted": "",
|
||||
"jobfieldchanged": "",
|
||||
"jobimported": "",
|
||||
"jobinproductionchange": "",
|
||||
"jobmodifylbradj": "",
|
||||
"jobspartsorder": "",
|
||||
"jobspartsreturn": "",
|
||||
"jobstatuschange": "",
|
||||
"jobsupplement": ""
|
||||
}
|
||||
},
|
||||
"billlines": {
|
||||
"actions": {
|
||||
"newline": ""
|
||||
@@ -211,6 +231,12 @@
|
||||
"deliver": {
|
||||
"templates": ""
|
||||
},
|
||||
"dms": {
|
||||
"default_journal": "",
|
||||
"dms_acctnumber": "",
|
||||
"dms_wip_acctnumber": "",
|
||||
"mappingname": ""
|
||||
},
|
||||
"email": "",
|
||||
"enforce_class": "",
|
||||
"enforce_referral": "",
|
||||
@@ -426,6 +452,7 @@
|
||||
"production_statuses": ""
|
||||
},
|
||||
"target_touchtime": "",
|
||||
"tt_allow_post_to_invoiced": "",
|
||||
"use_fippa": "",
|
||||
"website": "",
|
||||
"zip_post": ""
|
||||
@@ -446,6 +473,10 @@
|
||||
"defaultcostsmapping": "",
|
||||
"defaultprofitsmapping": "",
|
||||
"deliverchecklist": "",
|
||||
"dms": {
|
||||
"cdk_dealerid": "",
|
||||
"title": ""
|
||||
},
|
||||
"employees": "",
|
||||
"insurancecos": "",
|
||||
"intakechecklist": "",
|
||||
@@ -491,6 +522,7 @@
|
||||
},
|
||||
"labels": {
|
||||
"addtoproduction": "",
|
||||
"allow_text_message": "",
|
||||
"checklist": "",
|
||||
"printpack": "",
|
||||
"removefromproduction": ""
|
||||
@@ -793,6 +825,9 @@
|
||||
"exportlogs": {
|
||||
"fields": {
|
||||
"createdat": ""
|
||||
},
|
||||
"labels": {
|
||||
"attempts": ""
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
@@ -857,6 +892,7 @@
|
||||
"message": "",
|
||||
"monday": "",
|
||||
"na": "N / A",
|
||||
"newpassword": "",
|
||||
"no": "",
|
||||
"nointernet": "",
|
||||
"nointernet_sub": "",
|
||||
@@ -877,6 +913,7 @@
|
||||
"sendagain": "",
|
||||
"sendby": "",
|
||||
"signin": "",
|
||||
"sms": "",
|
||||
"sub_status": {
|
||||
"expired": ""
|
||||
},
|
||||
@@ -1027,6 +1064,7 @@
|
||||
"changestatus": "Cambiar Estado",
|
||||
"convert": "Convertir",
|
||||
"deliver": "",
|
||||
"dmsautoallocate": "",
|
||||
"export": "",
|
||||
"exportcustdata": "",
|
||||
"exportselected": "",
|
||||
@@ -1036,6 +1074,7 @@
|
||||
"intake": "",
|
||||
"manualnew": "",
|
||||
"mark": "",
|
||||
"markasexported": "",
|
||||
"markpstexempt": "",
|
||||
"markpstexemptconfirm": "",
|
||||
"postbills": "Contabilizar facturas",
|
||||
@@ -1046,6 +1085,7 @@
|
||||
"schedule": "Programar",
|
||||
"sendcsi": "",
|
||||
"sync": "",
|
||||
"uninvoice": "",
|
||||
"unvoid": "",
|
||||
"viewchecklist": "",
|
||||
"viewdetail": ""
|
||||
@@ -1104,6 +1144,14 @@
|
||||
"ded_amt": "Deducible",
|
||||
"ded_status": "Estado deducible",
|
||||
"depreciation_taxes": "Depreciación / Impuestos",
|
||||
"dms": {
|
||||
"center": "",
|
||||
"cost": "",
|
||||
"cost_dms_acctnumber": "",
|
||||
"dms_wip_acctnumber": "",
|
||||
"sale": "",
|
||||
"sale_dms_acctnumber": ""
|
||||
},
|
||||
"driveable": "",
|
||||
"employee_body": "",
|
||||
"employee_csr": "Representante de servicio al cliente.",
|
||||
@@ -1227,6 +1275,7 @@
|
||||
"servicecar": "Auto de servicio",
|
||||
"servicing_dealer": "Distribuidor de servicio",
|
||||
"servicing_dealer_contact": "Servicio Contacto con el concesionario",
|
||||
"special_coverage_policy": "Política de cobertura especial",
|
||||
"specialcoveragepolicy": "Política de cobertura especial",
|
||||
"state_tax_rate": "",
|
||||
"status": "Estado del trabajo",
|
||||
@@ -1264,6 +1313,7 @@
|
||||
"additionaltotal": "",
|
||||
"adjustmentrate": "",
|
||||
"adjustments": "",
|
||||
"adminwarning": "",
|
||||
"allocations": "",
|
||||
"alreadyclosed": "",
|
||||
"appointmentconfirmation": "¿Enviar confirmación al cliente?",
|
||||
@@ -1316,6 +1366,7 @@
|
||||
"waived": ""
|
||||
},
|
||||
"deleteconfirm": "",
|
||||
"deletedelivery": "",
|
||||
"deleteintake": "",
|
||||
"deliverchecklist": "",
|
||||
"difference": "",
|
||||
@@ -1953,12 +2004,15 @@
|
||||
"bills": "",
|
||||
"exportlogs": "",
|
||||
"jobs": "",
|
||||
"parts_orders": "",
|
||||
"payments": "",
|
||||
"scoreboard": "",
|
||||
"timetickets": ""
|
||||
},
|
||||
"vendor": ""
|
||||
},
|
||||
"templates": {
|
||||
"anticipated_revenue": "",
|
||||
"attendance_detail": "",
|
||||
"attendance_employee": "",
|
||||
"attendance_summary": "",
|
||||
@@ -1994,11 +2048,13 @@
|
||||
"job_costing_ro_date_summary": "",
|
||||
"job_costing_ro_estimator": "",
|
||||
"job_costing_ro_ins_co": "",
|
||||
"lag_time": "",
|
||||
"open_orders": "",
|
||||
"open_orders_csr": "",
|
||||
"open_orders_estimator": "",
|
||||
"open_orders_ins_co": "",
|
||||
"parts_backorder": "",
|
||||
"parts_not_recieved": "",
|
||||
"payments_by_date": "",
|
||||
"payments_by_date_type": "",
|
||||
"production_by_csr": "",
|
||||
@@ -2015,6 +2071,8 @@
|
||||
"purchases_grouped_by_vendor_detailed": "",
|
||||
"purchases_grouped_by_vendor_summary": "",
|
||||
"schedule": "",
|
||||
"scoreboard_detail": "",
|
||||
"scoreboard_summary": "",
|
||||
"supplement_ratio_ins_co": "",
|
||||
"thank_you_date": "",
|
||||
"timetickets": "",
|
||||
@@ -2054,6 +2112,11 @@
|
||||
"updated": ""
|
||||
}
|
||||
},
|
||||
"scoredboard": {
|
||||
"successes": {
|
||||
"updated": ""
|
||||
}
|
||||
},
|
||||
"tech": {
|
||||
"fields": {
|
||||
"employeeid": "",
|
||||
@@ -2221,6 +2284,7 @@
|
||||
},
|
||||
"user": {
|
||||
"actions": {
|
||||
"changepassword": "",
|
||||
"signout": "desconectar",
|
||||
"updateprofile": "Actualización del perfil"
|
||||
},
|
||||
@@ -2235,6 +2299,9 @@
|
||||
},
|
||||
"labels": {
|
||||
"actions": ""
|
||||
},
|
||||
"successess": {
|
||||
"passwordchanged": ""
|
||||
}
|
||||
},
|
||||
"vehicles": {
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"fields": {
|
||||
"alt_transport": "",
|
||||
"color": "",
|
||||
"note": "",
|
||||
"time": "",
|
||||
"title": "Titre"
|
||||
},
|
||||
@@ -52,6 +53,7 @@
|
||||
"nocompletingjobs": "",
|
||||
"nodateselected": "Aucune date n'a été sélectionnée.",
|
||||
"priorappointments": "Rendez-vous précédents",
|
||||
"reminder": "",
|
||||
"scheduledfor": "Rendez-vous prévu pour:",
|
||||
"smartscheduling": "",
|
||||
"suggesteddates": ""
|
||||
@@ -82,6 +84,24 @@
|
||||
"values": ""
|
||||
}
|
||||
},
|
||||
"audit_trail": {
|
||||
"messages": {
|
||||
"billposted": "",
|
||||
"billupdated": "",
|
||||
"jobassignmentchange": "",
|
||||
"jobassignmentremoved": "",
|
||||
"jobchecklist": "",
|
||||
"jobconverted": "",
|
||||
"jobfieldchanged": "",
|
||||
"jobimported": "",
|
||||
"jobinproductionchange": "",
|
||||
"jobmodifylbradj": "",
|
||||
"jobspartsorder": "",
|
||||
"jobspartsreturn": "",
|
||||
"jobstatuschange": "",
|
||||
"jobsupplement": ""
|
||||
}
|
||||
},
|
||||
"billlines": {
|
||||
"actions": {
|
||||
"newline": ""
|
||||
@@ -211,6 +231,12 @@
|
||||
"deliver": {
|
||||
"templates": ""
|
||||
},
|
||||
"dms": {
|
||||
"default_journal": "",
|
||||
"dms_acctnumber": "",
|
||||
"dms_wip_acctnumber": "",
|
||||
"mappingname": ""
|
||||
},
|
||||
"email": "",
|
||||
"enforce_class": "",
|
||||
"enforce_referral": "",
|
||||
@@ -426,6 +452,7 @@
|
||||
"production_statuses": ""
|
||||
},
|
||||
"target_touchtime": "",
|
||||
"tt_allow_post_to_invoiced": "",
|
||||
"use_fippa": "",
|
||||
"website": "",
|
||||
"zip_post": ""
|
||||
@@ -446,6 +473,10 @@
|
||||
"defaultcostsmapping": "",
|
||||
"defaultprofitsmapping": "",
|
||||
"deliverchecklist": "",
|
||||
"dms": {
|
||||
"cdk_dealerid": "",
|
||||
"title": ""
|
||||
},
|
||||
"employees": "",
|
||||
"insurancecos": "",
|
||||
"intakechecklist": "",
|
||||
@@ -491,6 +522,7 @@
|
||||
},
|
||||
"labels": {
|
||||
"addtoproduction": "",
|
||||
"allow_text_message": "",
|
||||
"checklist": "",
|
||||
"printpack": "",
|
||||
"removefromproduction": ""
|
||||
@@ -793,6 +825,9 @@
|
||||
"exportlogs": {
|
||||
"fields": {
|
||||
"createdat": ""
|
||||
},
|
||||
"labels": {
|
||||
"attempts": ""
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
@@ -857,6 +892,7 @@
|
||||
"message": "",
|
||||
"monday": "",
|
||||
"na": "N / A",
|
||||
"newpassword": "",
|
||||
"no": "",
|
||||
"nointernet": "",
|
||||
"nointernet_sub": "",
|
||||
@@ -877,6 +913,7 @@
|
||||
"sendagain": "",
|
||||
"sendby": "",
|
||||
"signin": "",
|
||||
"sms": "",
|
||||
"sub_status": {
|
||||
"expired": ""
|
||||
},
|
||||
@@ -1027,6 +1064,7 @@
|
||||
"changestatus": "Changer le statut",
|
||||
"convert": "Convertir",
|
||||
"deliver": "",
|
||||
"dmsautoallocate": "",
|
||||
"export": "",
|
||||
"exportcustdata": "",
|
||||
"exportselected": "",
|
||||
@@ -1036,6 +1074,7 @@
|
||||
"intake": "",
|
||||
"manualnew": "",
|
||||
"mark": "",
|
||||
"markasexported": "",
|
||||
"markpstexempt": "",
|
||||
"markpstexemptconfirm": "",
|
||||
"postbills": "Poster des factures",
|
||||
@@ -1046,6 +1085,7 @@
|
||||
"schedule": "Programme",
|
||||
"sendcsi": "",
|
||||
"sync": "",
|
||||
"uninvoice": "",
|
||||
"unvoid": "",
|
||||
"viewchecklist": "",
|
||||
"viewdetail": ""
|
||||
@@ -1104,6 +1144,14 @@
|
||||
"ded_amt": "Déductible",
|
||||
"ded_status": "Statut de franchise",
|
||||
"depreciation_taxes": "Amortissement / taxes",
|
||||
"dms": {
|
||||
"center": "",
|
||||
"cost": "",
|
||||
"cost_dms_acctnumber": "",
|
||||
"dms_wip_acctnumber": "",
|
||||
"sale": "",
|
||||
"sale_dms_acctnumber": ""
|
||||
},
|
||||
"driveable": "",
|
||||
"employee_body": "",
|
||||
"employee_csr": "représentant du service à la clientèle",
|
||||
@@ -1227,6 +1275,7 @@
|
||||
"servicecar": "Voiture de service",
|
||||
"servicing_dealer": "Concessionnaire",
|
||||
"servicing_dealer_contact": "Contacter le concessionnaire",
|
||||
"special_coverage_policy": "Politique de couverture spéciale",
|
||||
"specialcoveragepolicy": "Politique de couverture spéciale",
|
||||
"state_tax_rate": "",
|
||||
"status": "Statut de l'emploi",
|
||||
@@ -1264,6 +1313,7 @@
|
||||
"additionaltotal": "",
|
||||
"adjustmentrate": "",
|
||||
"adjustments": "",
|
||||
"adminwarning": "",
|
||||
"allocations": "",
|
||||
"alreadyclosed": "",
|
||||
"appointmentconfirmation": "Envoyer une confirmation au client?",
|
||||
@@ -1316,6 +1366,7 @@
|
||||
"waived": ""
|
||||
},
|
||||
"deleteconfirm": "",
|
||||
"deletedelivery": "",
|
||||
"deleteintake": "",
|
||||
"deliverchecklist": "",
|
||||
"difference": "",
|
||||
@@ -1953,12 +2004,15 @@
|
||||
"bills": "",
|
||||
"exportlogs": "",
|
||||
"jobs": "",
|
||||
"parts_orders": "",
|
||||
"payments": "",
|
||||
"scoreboard": "",
|
||||
"timetickets": ""
|
||||
},
|
||||
"vendor": ""
|
||||
},
|
||||
"templates": {
|
||||
"anticipated_revenue": "",
|
||||
"attendance_detail": "",
|
||||
"attendance_employee": "",
|
||||
"attendance_summary": "",
|
||||
@@ -1994,11 +2048,13 @@
|
||||
"job_costing_ro_date_summary": "",
|
||||
"job_costing_ro_estimator": "",
|
||||
"job_costing_ro_ins_co": "",
|
||||
"lag_time": "",
|
||||
"open_orders": "",
|
||||
"open_orders_csr": "",
|
||||
"open_orders_estimator": "",
|
||||
"open_orders_ins_co": "",
|
||||
"parts_backorder": "",
|
||||
"parts_not_recieved": "",
|
||||
"payments_by_date": "",
|
||||
"payments_by_date_type": "",
|
||||
"production_by_csr": "",
|
||||
@@ -2015,6 +2071,8 @@
|
||||
"purchases_grouped_by_vendor_detailed": "",
|
||||
"purchases_grouped_by_vendor_summary": "",
|
||||
"schedule": "",
|
||||
"scoreboard_detail": "",
|
||||
"scoreboard_summary": "",
|
||||
"supplement_ratio_ins_co": "",
|
||||
"thank_you_date": "",
|
||||
"timetickets": "",
|
||||
@@ -2054,6 +2112,11 @@
|
||||
"updated": ""
|
||||
}
|
||||
},
|
||||
"scoredboard": {
|
||||
"successes": {
|
||||
"updated": ""
|
||||
}
|
||||
},
|
||||
"tech": {
|
||||
"fields": {
|
||||
"employeeid": "",
|
||||
@@ -2221,6 +2284,7 @@
|
||||
},
|
||||
"user": {
|
||||
"actions": {
|
||||
"changepassword": "",
|
||||
"signout": "Déconnexion",
|
||||
"updateprofile": "Mettre à jour le profil"
|
||||
},
|
||||
@@ -2235,6 +2299,9 @@
|
||||
},
|
||||
"labels": {
|
||||
"actions": ""
|
||||
},
|
||||
"successess": {
|
||||
"passwordchanged": ""
|
||||
}
|
||||
},
|
||||
"vehicles": {
|
||||
|
||||
31
client/src/utils/AuditTrailMappings.js
Normal file
31
client/src/utils/AuditTrailMappings.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import i18n from "i18next";
|
||||
|
||||
const AuditTrailMapping = {
|
||||
jobstatuschange: (status) =>
|
||||
i18n.t("audit_trail.messages.jobstatuschange", { status }),
|
||||
jobsupplement: () => i18n.t("audit_trail.messages.jobsupplement"),
|
||||
jobimported: () => i18n.t("audit_trail.messages.jobimported"),
|
||||
jobconverted: (ro_number) =>
|
||||
i18n.t("audit_trail.messages.jobconverted", { ro_number }),
|
||||
jobfieldchange: (field, value) =>
|
||||
i18n.t("audit_trail.messages.jobfieldchanged", { field, value }),
|
||||
jobspartsorder: (order_number) =>
|
||||
i18n.t("audit_trail.messages.jobspartsorder", { order_number }),
|
||||
jobspartsreturn: (order_number) =>
|
||||
i18n.t("audit_trail.messages.jobspartsreturn", { order_number }),
|
||||
jobmodifylbradj: () => i18n.t("audit_trail.messages.jobmodifylbradj", {}),
|
||||
billposted: (invoice_number) =>
|
||||
i18n.t("audit_trail.messages.billposted", { invoice_number }),
|
||||
billupdated: (invoice_number) =>
|
||||
i18n.t("audit_trail.messages.billupdated", { invoice_number }),
|
||||
jobassignmentchange: (operation, name) =>
|
||||
i18n.t("audit_trail.messages.jobassignmentchange", { operation, name }),
|
||||
jobassignmentremoved: (operation) =>
|
||||
i18n.t("audit_trail.messages.jobassignmentremoved", { operation }),
|
||||
jobinproductionchange: (inproduction) =>
|
||||
i18n.t("audit_trail.messages.jobinproductionchange", { inproduction }),
|
||||
jobchecklist: (type, inproduction, status) =>
|
||||
i18n.t("audit_trail.messages.jobchecklist", { type, inproduction, status }),
|
||||
};
|
||||
|
||||
export default AuditTrailMapping;
|
||||
@@ -6,7 +6,6 @@ import { WebSocketLink } from "@apollo/client/link/ws";
|
||||
import { getMainDefinition } from "@apollo/client/utilities";
|
||||
//import { split } from "apollo-link";
|
||||
import apolloLogger from "apollo-link-logger";
|
||||
import axios from "axios";
|
||||
import { auth } from "../firebase/firebase.utils";
|
||||
import errorLink from "../graphql/apollo-error-handling";
|
||||
|
||||
@@ -48,7 +47,7 @@ const roundTripLink = new ApolloLink((operation, forward) => {
|
||||
});
|
||||
|
||||
const TrackExecutionTime = async (operationName, time) => {
|
||||
await axios.post("/ioevent", { operationName, time, dbevent: true });
|
||||
//await axios.post("/ioevent", { operationName, time, dbevent: true });
|
||||
};
|
||||
|
||||
const subscriptionMiddleware = {
|
||||
|
||||
@@ -1190,6 +1190,66 @@ export const TemplateList = (type, context) => {
|
||||
field: i18n.t("jobs.fields.date_open"),
|
||||
},
|
||||
},
|
||||
lag_time: {
|
||||
title: i18n.t("reportcenter.templates.lag_time"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.lag_time"),
|
||||
key: "lag_time",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||
field: i18n.t("jobs.fields.date_invoiced"),
|
||||
},
|
||||
},
|
||||
parts_not_recieved: {
|
||||
title: i18n.t("reportcenter.templates.parts_not_recieved"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.parts_not_recieved"),
|
||||
key: "parts_not_recieved",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.parts_orders"),
|
||||
field: i18n.t("parts_orders.fields.order_date"),
|
||||
},
|
||||
},
|
||||
scoreboard_detail: {
|
||||
title: i18n.t("reportcenter.templates.scoreboard_detail"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.scoreboard_detail"),
|
||||
key: "scoreboard_detail",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.scoreboard"),
|
||||
field: i18n.t("scoreboard.fields.date"),
|
||||
},
|
||||
},
|
||||
scoreboard_summary: {
|
||||
title: i18n.t("reportcenter.templates.scoreboard_summary"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.scoreboard_summary"),
|
||||
key: "scoreboard_summary",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.scoreboard"),
|
||||
field: i18n.t("scoreboard.fields.date"),
|
||||
},
|
||||
},
|
||||
anticipated_revenue: {
|
||||
title: i18n.t("reportcenter.templates.anticipated_revenue"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.anticipated_revenue"),
|
||||
key: "anticipated_revenue",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||
field: i18n.t("jobs.fields.scheduled_completion"), // Also date invoice.
|
||||
},
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(!type || type === "courtesycarcontract"
|
||||
|
||||
724
client/yarn.lock
724
client/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- id
|
||||
- new_val
|
||||
- old_val
|
||||
- operation
|
||||
- schemaname
|
||||
- tabname
|
||||
- useremail
|
||||
- created
|
||||
- bodyshopid
|
||||
- recordid
|
||||
computed_fields: []
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
role: user
|
||||
table:
|
||||
name: audit_trail
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -0,0 +1,6 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: audit_trail
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
@@ -0,0 +1,10 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."audit_trail" ADD COLUMN "schemaname" text;
|
||||
type: run_sql
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."audit_trail" ALTER COLUMN "schemaname" DROP NOT NULL;
|
||||
type: run_sql
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."audit_trail" DROP COLUMN "schemaname" CASCADE;
|
||||
type: run_sql
|
||||
@@ -0,0 +1,10 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."audit_trail" ADD COLUMN "tabname" text;
|
||||
type: run_sql
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."audit_trail" ALTER COLUMN "tabname" DROP NOT NULL;
|
||||
type: run_sql
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."audit_trail" DROP COLUMN "tabname" CASCADE;
|
||||
type: run_sql
|
||||
@@ -0,0 +1,10 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."audit_trail" ADD COLUMN "recordid" uuid;
|
||||
type: run_sql
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."audit_trail" ALTER COLUMN "recordid" DROP NOT NULL;
|
||||
type: run_sql
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."audit_trail" DROP COLUMN "recordid" CASCADE;
|
||||
type: run_sql
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."audit_trail" DROP COLUMN "jobid";
|
||||
type: run_sql
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."audit_trail" ADD COLUMN "jobid" uuid NULL;
|
||||
type: run_sql
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."audit_trail" DROP COLUMN "billid";
|
||||
type: run_sql
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."audit_trail" ADD COLUMN "billid" uuid NULL;
|
||||
type: run_sql
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: alter table "public"."audit_trail" drop constraint "audit_trail_billid_fkey";
|
||||
type: run_sql
|
||||
@@ -0,0 +1,10 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: |-
|
||||
alter table "public"."audit_trail"
|
||||
add constraint "audit_trail_billid_fkey"
|
||||
foreign key ("billid")
|
||||
references "public"."bills"
|
||||
("id") on update cascade on delete cascade;
|
||||
type: run_sql
|
||||
@@ -0,0 +1,12 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: |-
|
||||
alter table "public"."audit_trail" drop constraint "audit_trail_billid_fkey",
|
||||
add constraint "audit_trail_billid_fkey"
|
||||
foreign key ("billid")
|
||||
references "public"."bills"
|
||||
("id")
|
||||
on update cascade
|
||||
on delete cascade;
|
||||
type: run_sql
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user