Compare commits
39 Commits
developmen
...
hotfix/202
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58c6ba2f87 | ||
|
|
343274e1e2 | ||
|
|
09ce5ac892 | ||
|
|
c8506387f6 | ||
|
|
79f2c7dd3d | ||
|
|
d91a83a137 | ||
|
|
0b21b8d976 | ||
|
|
842cb54867 | ||
|
|
a5628188d8 | ||
|
|
1b0e37be45 | ||
|
|
48ecfe0d98 | ||
|
|
b5b4a3a4f9 | ||
|
|
585d585cd3 | ||
|
|
8a7a5f35c1 | ||
|
|
b24c5e7cf1 | ||
|
|
e8b7e2f0b9 | ||
|
|
3e59c60477 | ||
|
|
e89b4fe2a4 | ||
|
|
73b0542b62 | ||
|
|
c9812c36c0 | ||
|
|
d62a2c0aaf | ||
|
|
ee15f063ce | ||
|
|
5f0a683ec1 | ||
|
|
6ae9de7df3 | ||
|
|
19aa41ab5b | ||
|
|
0494b3a9a6 | ||
|
|
01d9dc9033 | ||
|
|
47b8d8d8b2 | ||
|
|
6e3453cd90 | ||
|
|
4e10451bea | ||
|
|
81d5c8f449 | ||
|
|
79fa71fc84 | ||
|
|
f6378daa89 | ||
|
|
f2a4eb1b65 | ||
|
|
2d19c35177 | ||
|
|
9787f7e377 | ||
|
|
3abac3e7ac | ||
|
|
fddf75b40b | ||
|
|
a7878243ee |
@@ -22,7 +22,7 @@ hooks.js:
|
||||
module.exports = [
|
||||
{
|
||||
path: "/pull",
|
||||
command: "git pull && npm i",
|
||||
command: "git pull && yarn && pm2 restart 0",
|
||||
cwd: "/home/ubuntu/io/",
|
||||
method: "post",
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<babeledit_project version="1.2" be_version="2.7.1">
|
||||
<babeledit_project be_version="2.7.1" version="1.2">
|
||||
<!--
|
||||
|
||||
BabelEdit project file
|
||||
@@ -3339,6 +3339,53 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<folder_node>
|
||||
<name>jc_hourly_rates</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>mapa</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>mash</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<concept_node>
|
||||
<name>lastnumberworkingdays</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -10757,6 +10804,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>updating</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
@@ -12947,6 +13015,48 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>nointernet</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>nointernet_sub</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>none</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -20437,6 +20547,48 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>calc_repair_days</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>calc_repair_days_tt</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<folder_node>
|
||||
<name>cards</name>
|
||||
<children>
|
||||
@@ -22137,6 +22289,27 @@
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<concept_node>
|
||||
<name>prt_dsmk_total</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>rates</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -27817,6 +27990,48 @@
|
||||
<folder_node>
|
||||
<name>labels</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>noneselected</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>onenamerequired</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>vendorcategory</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -30250,6 +30465,32 @@
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>successes</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>updated</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"dinero.js": "^1.8.1",
|
||||
"dotenv": "^9.0.2",
|
||||
"env-cmd": "^10.1.0",
|
||||
"exifr": "^7.0.0",
|
||||
"firebase": "^8.6.0",
|
||||
"graphql": "^15.5.0",
|
||||
"i18next": "^20.2.2",
|
||||
|
||||
@@ -14,6 +14,7 @@ if (process.env.NODE_ENV === "production") LogRocket.init("gvfvfw/bodyshopapp");
|
||||
|
||||
export default function AppContainer() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<ApolloProvider client={client}>
|
||||
<ConfigProvider
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Button, Result } from "antd";
|
||||
import React, { lazy, Suspense, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -8,6 +9,8 @@ import ErrorBoundary from "../components/error-boundary/error-boundary.component
|
||||
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
|
||||
import AboutPage from "../pages/about/about.page";
|
||||
import TechPageContainer from "../pages/tech/tech.page.container";
|
||||
import { setOnline } from "../redux/application/application.actions";
|
||||
import { selectOnline } from "../redux/application/application.selectors";
|
||||
import { checkUserSession } from "../redux/user/user.actions";
|
||||
import { selectCurrentUser } from "../redux/user/user.selectors";
|
||||
import PrivateRoute from "../utils/private-route";
|
||||
@@ -27,12 +30,14 @@ const MobilePaymentContainer = lazy(() =>
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
online: selectOnline,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
checkUserSession: () => dispatch(checkUserSession()),
|
||||
setOnline: (isOnline) => dispatch(setOnline(isOnline)),
|
||||
});
|
||||
|
||||
export function App({ checkUserSession, currentUser }) {
|
||||
export function App({ checkUserSession, currentUser, online, setOnline }) {
|
||||
useEffect(() => {
|
||||
checkUserSession();
|
||||
}, [checkUserSession]);
|
||||
@@ -42,10 +47,38 @@ export function App({ checkUserSession, currentUser }) {
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
window.addEventListener("offline", function (e) {
|
||||
console.log("Internet connection lost.");
|
||||
setOnline(false);
|
||||
});
|
||||
|
||||
window.addEventListener("online", function (e) {
|
||||
setOnline(true);
|
||||
});
|
||||
|
||||
if (currentUser.authorized === null) {
|
||||
return <LoadingSpinner message={t("general.labels.loggingin")} />;
|
||||
}
|
||||
|
||||
if (!online)
|
||||
return (
|
||||
<Result
|
||||
status="warning"
|
||||
title={t("general.labels.nointernet")}
|
||||
subTitle={t("general.labels.nointernet_sub")}
|
||||
extra={
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
window.location.reload();
|
||||
}}
|
||||
>
|
||||
{t("general.actions.refresh")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Suspense fallback={<LoadingSpinner message="ImEX Online" />}>
|
||||
|
||||
@@ -7,7 +7,7 @@ import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import PaymentExportButton from "../payment-export-button/payment-export-button.component";
|
||||
import { PaymentsExportAllButton } from "../payments-export-all-button/payments-export-all-button.component";
|
||||
import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component";
|
||||
|
||||
export default function AccountingPayablesTableComponent({
|
||||
loading,
|
||||
|
||||
@@ -5,6 +5,8 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { INSERT_NEW_DOCUMENT } from "../../graphql/documents.queries";
|
||||
import { axiosAuthInterceptorId } from "../../utils/CleanAxios";
|
||||
import client from "../../utils/GraphQLClient";
|
||||
import exifr from "exifr";
|
||||
|
||||
//Context: currentUserEmail, bodyshop, jobid, invoiceid
|
||||
|
||||
//Required to prevent headers from getting set and rejected from Cloudinary.
|
||||
@@ -85,6 +87,7 @@ export const uploadToCloudinary = async (
|
||||
if (!!onProgress) onProgress({ percent: (e.loaded / e.total) * 100 });
|
||||
},
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
||||
@@ -122,6 +125,12 @@ export const uploadToCloudinary = async (
|
||||
}
|
||||
|
||||
//Insert the document with the matching key.
|
||||
let takenat;
|
||||
if (fileType.includes("image")) {
|
||||
const exif = await exifr.parse(file);
|
||||
|
||||
takenat = exif && exif.DateTimeOriginal;
|
||||
}
|
||||
const documentInsert = await client.mutate({
|
||||
mutation: INSERT_NEW_DOCUMENT,
|
||||
variables: {
|
||||
@@ -132,9 +141,10 @@ export const uploadToCloudinary = async (
|
||||
uploaded_by: uploaded_by,
|
||||
key: key,
|
||||
type: fileType,
|
||||
extension: extension,
|
||||
extension: cloudinaryUploadResponse.data.format || extension,
|
||||
bodyshopid: bodyshop.id,
|
||||
size: cloudinaryUploadResponse.data.bytes || file.size,
|
||||
takenat,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -166,6 +176,7 @@ export const uploadToCloudinary = async (
|
||||
}
|
||||
};
|
||||
|
||||
//Also needs to be updated in media JS and mobile app.
|
||||
export function DetermineFileType(filetype) {
|
||||
if (!filetype) return "auto";
|
||||
else if (filetype.startsWith("image")) return "image";
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from "../../../../redux/user/user.selectors";
|
||||
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";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -133,7 +134,14 @@ export function JobChecklistForm({
|
||||
initialValues={{
|
||||
...(type === "intake" && {
|
||||
addToProduction: true,
|
||||
scheduled_completion: job && job.scheduled_completion,
|
||||
scheduled_completion:
|
||||
(job && job.scheduled_completion) ||
|
||||
moment().businessAdd(
|
||||
(job.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
job.larhrs.aggregate.sum.mod_lb_hrs) /
|
||||
bodyshop.target_touchtime,
|
||||
"days"
|
||||
),
|
||||
scheduled_delivery: job && job.scheduled_delivery,
|
||||
}),
|
||||
...(type === "deliver" && {
|
||||
|
||||
@@ -155,7 +155,14 @@ export function JobLinesComponent({
|
||||
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
|
||||
ellipsis: true,
|
||||
render: (text, record) => (
|
||||
<CurrencyFormatter>{record.act_price}</CurrencyFormatter>
|
||||
<>
|
||||
<CurrencyFormatter>{record.act_price}</CurrencyFormatter>
|
||||
{record.prt_dsmk_p !== 0 && (
|
||||
<span
|
||||
style={{ marginLeft: ".2rem" }}
|
||||
>{`(${record.prt_dsmk_p}%)`}</span>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -313,9 +320,12 @@ export function JobLinesComponent({
|
||||
if (e.key === "clear") {
|
||||
setSelectedLines([]);
|
||||
} else {
|
||||
const markedTypes = [e.key];
|
||||
if (e.key === "PAN") markedTypes.push("PAP");
|
||||
if (e.key === "PAS") markedTypes.push("PASL");
|
||||
setSelectedLines([
|
||||
...selectedLines,
|
||||
...jobLines.filter((item) => item.part_type === e.key),
|
||||
...jobLines.filter((item) => markedTypes.includes(item.part_type)),
|
||||
]);
|
||||
}
|
||||
};
|
||||
@@ -378,7 +388,7 @@ export function JobLinesComponent({
|
||||
setState({
|
||||
...state,
|
||||
filteredInfo: {
|
||||
part_type: ["PAN,PAL,PAA,PAS,PASL"],
|
||||
part_type: ["PAN,PAL,PAA,PAP,PAS,PASL"],
|
||||
},
|
||||
});
|
||||
}}
|
||||
|
||||
@@ -123,7 +123,7 @@ export default function JobLinesUpsertModalComponent({
|
||||
// }),
|
||||
// ]}
|
||||
>
|
||||
<InputCurrency />
|
||||
<InputNumber precision={1} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow>
|
||||
|
||||
@@ -69,17 +69,28 @@ export default function JobTotalsTableParts({ job }) {
|
||||
x: true,
|
||||
}}
|
||||
summary={() => (
|
||||
<Table.Summary.Row>
|
||||
<Table.Summary.Cell>
|
||||
<strong>{t("jobs.labels.partstotal")}</strong>
|
||||
</Table.Summary.Cell>
|
||||
<>
|
||||
<Table.Summary.Row>
|
||||
<Table.Summary.Cell>
|
||||
{t("jobs.labels.prt_dsmk_total")}
|
||||
</Table.Summary.Cell>
|
||||
|
||||
<Table.Summary.Cell align="right">
|
||||
<strong>
|
||||
{Dinero(job.job_totals.parts.parts.total).toFormat()}
|
||||
</strong>
|
||||
</Table.Summary.Cell>
|
||||
</Table.Summary.Row>
|
||||
<Table.Summary.Cell align="right">
|
||||
{Dinero(job.job_totals.parts.parts.prt_dsmk_total).toFormat()}
|
||||
</Table.Summary.Cell>
|
||||
</Table.Summary.Row>
|
||||
<Table.Summary.Row>
|
||||
<Table.Summary.Cell>
|
||||
<strong>{t("jobs.labels.partstotal")}</strong>
|
||||
</Table.Summary.Cell>
|
||||
|
||||
<Table.Summary.Cell align="right">
|
||||
<strong>
|
||||
{Dinero(job.job_totals.parts.parts.total).toFormat()}
|
||||
</strong>
|
||||
</Table.Summary.Cell>
|
||||
</Table.Summary.Row>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -32,7 +32,7 @@ mutation UNVOID_JOB($jobId: uuid!) {
|
||||
}
|
||||
insert_notes(objects: {jobid: $jobId, audit: true, created_by: "${
|
||||
currentUser.email
|
||||
}", text: "${t("jobs.labels.unvoidnote", { email: currentUser.email })}"}) {
|
||||
}", text: "${t("jobs.labels.unvoidnote")}"}) {
|
||||
returning {
|
||||
id
|
||||
}
|
||||
|
||||
@@ -124,10 +124,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
|
||||
data: {
|
||||
created_by: currentUser.email,
|
||||
audit: true,
|
||||
text: t("jobs.labels.importnote", {
|
||||
date: moment().format("MM/DD/yyy"),
|
||||
time: moment().format("hh:mm a"),
|
||||
}),
|
||||
text: t("jobs.labels.importnote"),
|
||||
},
|
||||
},
|
||||
queued_for_parts: true,
|
||||
@@ -278,10 +275,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
|
||||
jobid: selectedJob,
|
||||
created_by: currentUser.email,
|
||||
audit: true,
|
||||
text: t("jobs.labels.supplementnote", {
|
||||
date: moment().format("MM/DD/yyy"),
|
||||
time: moment().format("hh:mm a"),
|
||||
}),
|
||||
text: t("jobs.labels.supplementnote"),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -26,12 +26,6 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
||||
const { getFieldValue } = form;
|
||||
return (
|
||||
<div>
|
||||
<JobsDetailRatesParts
|
||||
jobRO={false}
|
||||
expanded
|
||||
required={selected && true}
|
||||
form={form}
|
||||
/>
|
||||
<Collapse defaultActiveKey="insurance">
|
||||
<Collapse.Panel
|
||||
key="insurance"
|
||||
@@ -57,7 +51,13 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
||||
<FormDatePicker />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm">
|
||||
<Input />
|
||||
<Select>
|
||||
{bodyshop.md_ins_cos.map((s) => (
|
||||
<Select.Option key={s.name} value={s.name}>
|
||||
{s.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.ins_addr1")} name="ins_addr1">
|
||||
<Input />
|
||||
@@ -312,6 +312,12 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
||||
</LayoutFormRow>
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
<JobsDetailRatesParts
|
||||
jobRO={false}
|
||||
expanded
|
||||
required={selected && true}
|
||||
form={form}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -83,24 +83,12 @@ export default function JobsCreateVehicleInfoNewComponent() {
|
||||
<Form.Item
|
||||
label={t("vehicles.fields.plate_st")}
|
||||
name={["vehicle", "data", "plate_st"]}
|
||||
rules={[
|
||||
{
|
||||
required: state.vehicle.new,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input disabled={!state.vehicle.new} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("vehicles.fields.plate_no")}
|
||||
name={["vehicle", "data", "plate_no"]}
|
||||
rules={[
|
||||
{
|
||||
required: state.vehicle.new,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input disabled={!state.vehicle.new} />
|
||||
</Form.Item>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DatePicker, Form, Tooltip } from "antd";
|
||||
import { DatePicker, Form, Statistic, Tooltip } from "antd";
|
||||
import React, { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -23,6 +23,10 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
||||
);
|
||||
}, [job.status, bodyshop.md_ro_statuses.post_production_statuses]);
|
||||
|
||||
const calcRepairDays =
|
||||
job.joblines.reduce((acc, val) => acc + val.mod_lb_hrs, 0) /
|
||||
(bodyshop.target_touchtime === 0 ? 1 : bodyshop.target_touchtime);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormRow header={t("jobs.forms.estdates")}>
|
||||
@@ -52,6 +56,17 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
||||
<Form.Item label={t("jobs.fields.actual_in")} name="actual_in">
|
||||
<DateTimePicker disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Tooltip
|
||||
title={t("jobs.labels.calc_repair_days_tt", {
|
||||
target_touchtime: bodyshop.target_touchtime,
|
||||
})}
|
||||
>
|
||||
<Statistic
|
||||
value={calcRepairDays}
|
||||
precision={1}
|
||||
title={t("jobs.labels.calc_repair_days")}
|
||||
/>
|
||||
</Tooltip>
|
||||
</FormRow>
|
||||
<FormRow header={t("jobs.forms.repairdates")}>
|
||||
<Form.Item
|
||||
|
||||
@@ -54,7 +54,13 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm">
|
||||
<Input disabled={jobRO} />
|
||||
<Select disabled={jobRO}>
|
||||
{bodyshop.md_ins_cos.map((s) => (
|
||||
<Select.Option key={s.name} value={s.name}>
|
||||
{s.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.ins_addr1")} name="ins_addr1">
|
||||
<Input disabled={jobRO} />
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { DownCircleFilled } from "@ant-design/icons";
|
||||
import { useApolloClient, useMutation } from "@apollo/client";
|
||||
import { Button, Dropdown, Menu, notification, Popconfirm } from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -385,10 +384,7 @@ export function JobsDetailHeaderActions({
|
||||
jobid: job.id,
|
||||
created_by: currentUser.email,
|
||||
audit: true,
|
||||
text: t("jobs.labels.voidnote", {
|
||||
date: moment().format("MM/DD/yyy"),
|
||||
time: moment().format("hh:mm a"),
|
||||
}),
|
||||
text: t("jobs.labels.voidnote"),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -151,17 +151,21 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
||||
<Card
|
||||
style={{ height: "100%" }}
|
||||
title={
|
||||
<Link
|
||||
to={
|
||||
disabled
|
||||
? "#"
|
||||
: job.vehicle && `/manage/vehicles/${job.vehicle.id}`
|
||||
}
|
||||
>
|
||||
{`${job.v_model_yr || ""} ${job.v_color || ""}
|
||||
job.vehicle ? (
|
||||
<Link
|
||||
to={
|
||||
disabled
|
||||
? "#"
|
||||
: job.vehicle && `/manage/vehicles/${job.vehicle.id}`
|
||||
}
|
||||
>
|
||||
{`${job.v_model_yr || ""} ${job.v_color || ""}
|
||||
${job.v_make_desc || ""}
|
||||
${job.v_model_desc || ""}`}
|
||||
</Link>
|
||||
</Link>
|
||||
) : (
|
||||
<span></span>
|
||||
)
|
||||
}
|
||||
>
|
||||
<div>
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { Button } from "antd";
|
||||
import { Button, Space } from "antd";
|
||||
import axios from "axios";
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import cleanAxios from "../../utils/CleanAxios";
|
||||
import formatBytes from "../../utils/formatbytes";
|
||||
|
||||
export default function JobsDocumentsDownloadButton({ galleryImages }) {
|
||||
export default function JobsDocumentsDownloadButton({
|
||||
galleryImages,
|
||||
identifier,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [download, setDownload] = useState(null);
|
||||
const imagesToDownload = [
|
||||
...galleryImages.images.filter((image) => image.isSelected),
|
||||
...galleryImages.other.filter((image) => image.isSelected),
|
||||
@@ -18,13 +24,68 @@ export default function JobsDocumentsDownloadButton({ galleryImages }) {
|
||||
ids: imagesToDownload.map((_) => _.key),
|
||||
})
|
||||
.then((r) => {
|
||||
window.open(r.data);
|
||||
// window.open(r.data);
|
||||
downloadAs(
|
||||
r.data,
|
||||
`${identifier || "images"}.zip`,
|
||||
(progressEvent) => {
|
||||
const percentage = Math.round(
|
||||
(progressEvent.loaded * 100) / progressEvent.total
|
||||
);
|
||||
console.log(progressEvent, percentage);
|
||||
setDownload((currentDownloadState) => {
|
||||
return {
|
||||
downloaded: progressEvent.loaded || 0,
|
||||
speed:
|
||||
(progressEvent.loaded || 0) -
|
||||
((currentDownloadState && currentDownloadState.downloaded) ||
|
||||
0),
|
||||
};
|
||||
});
|
||||
},
|
||||
() => setDownload(null)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Button disabled={imagesToDownload.length < 1} onClick={handleDownload}>
|
||||
{t("documents.actions.download")}
|
||||
</Button>
|
||||
<>
|
||||
<Button
|
||||
loading={!!download}
|
||||
disabled={imagesToDownload.length < 1}
|
||||
onClick={handleDownload}
|
||||
>
|
||||
<Space>
|
||||
<span>{t("documents.actions.download")}</span>
|
||||
{download && (
|
||||
<span>{`(${formatBytes(download.downloaded)} @ ${formatBytes(
|
||||
download.speed
|
||||
)} / second)`}</span>
|
||||
)}
|
||||
</Space>
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const downloadAs = (url, name, onDownloadProgress, onCompleted) => {
|
||||
cleanAxios
|
||||
.get(url, {
|
||||
headers: {
|
||||
"Content-Type": "application/octet-stream",
|
||||
},
|
||||
responseType: "blob",
|
||||
onDownloadProgress: onDownloadProgress,
|
||||
})
|
||||
.then((response) => {
|
||||
onCompleted && onCompleted();
|
||||
const a = document.createElement("a");
|
||||
const url = window.URL.createObjectURL(response.data);
|
||||
a.href = url;
|
||||
a.download = name;
|
||||
a.click();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("error", err);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ function JobsDocumentsComponent({
|
||||
billId,
|
||||
billsCallback,
|
||||
totalSize,
|
||||
bodyshop,
|
||||
downloadIdentifier,
|
||||
ignoreSizeLimit,
|
||||
}) {
|
||||
const [galleryImages, setgalleryImages] = useState({ images: [], other: [] });
|
||||
@@ -31,7 +31,9 @@ function JobsDocumentsComponent({
|
||||
acc.images.push({
|
||||
src: `${
|
||||
process.env.REACT_APP_CLOUDINARY_ENDPOINT
|
||||
}/${DetermineFileType(value.type)}/upload/${value.key}`,
|
||||
}/${DetermineFileType(value.type)}/upload/${value.key}${
|
||||
value.extension ? `.${value.extension}` : ""
|
||||
}`,
|
||||
thumbnail: `${
|
||||
process.env.REACT_APP_CLOUDINARY_ENDPOINT
|
||||
}/${DetermineFileType(value.type)}/upload/${
|
||||
@@ -51,13 +53,17 @@ function JobsDocumentsComponent({
|
||||
let thumb;
|
||||
switch (fileType) {
|
||||
case "video":
|
||||
thumb = `${process.env.REACT_APP_CLOUDINARY_ENDPOINT}/${fileType}/upload/c_fill,f_png,h_250,w_250/${value.key}`;
|
||||
thumb = `${process.env.REACT_APP_CLOUDINARY_ENDPOINT}/${fileType}/upload/${process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS}/${value.key}`;
|
||||
break;
|
||||
case "raw":
|
||||
thumb = `${window.location.origin}/file.png`;
|
||||
break;
|
||||
default:
|
||||
thumb = `${process.env.REACT_APP_CLOUDINARY_ENDPOINT}/${fileType}/upload/${process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS}/${value.key}`;
|
||||
thumb = `${
|
||||
process.env.REACT_APP_CLOUDINARY_ENDPOINT
|
||||
}/${fileType}/upload/${
|
||||
process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS
|
||||
}/${value.key}${value.extension ? `.${value.extension}` : ""}`;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -65,9 +71,9 @@ function JobsDocumentsComponent({
|
||||
acc.other.push({
|
||||
src: `${
|
||||
process.env.REACT_APP_CLOUDINARY_ENDPOINT
|
||||
}/${fileType}/upload/${fileType === "video" ? "q_auto/" : ""}${
|
||||
value.key
|
||||
}${fileType === "raw" ? `.${value.extension}` : ""}`,
|
||||
}/${fileType}/upload/${value.key}${
|
||||
value.extension ? `.${value.extension}` : ""
|
||||
}`,
|
||||
thumbnail: thumb,
|
||||
tags: [
|
||||
{
|
||||
@@ -117,7 +123,10 @@ function JobsDocumentsComponent({
|
||||
galleryImages={galleryImages}
|
||||
setGalleryImages={setgalleryImages}
|
||||
/>
|
||||
<JobsDocumentsDownloadButton galleryImages={galleryImages} />
|
||||
<JobsDocumentsDownloadButton
|
||||
galleryImages={galleryImages}
|
||||
identifier={downloadIdentifier}
|
||||
/>
|
||||
<JobsDocumentsDeleteButton
|
||||
galleryImages={galleryImages}
|
||||
deletionCallback={billsCallback || refetch}
|
||||
@@ -173,6 +182,7 @@ function JobsDocumentsComponent({
|
||||
backgroundImage: <FileExcelFilled />,
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
cursor: "pointer",
|
||||
};
|
||||
}}
|
||||
onClickThumbnail={(index) => {
|
||||
|
||||
@@ -23,6 +23,7 @@ export default function JobsDocumentsContainer({
|
||||
return (
|
||||
<JobDocuments
|
||||
data={(data && data.documents) || documentsList || []}
|
||||
downloadIdentifier={data && data.jobs_by_pk.ro_number}
|
||||
totalSize={data && data.documents_aggregate.aggregate.sum.size}
|
||||
billId={billId}
|
||||
jobId={jobId}
|
||||
|
||||
@@ -110,7 +110,7 @@ export function LaborAllocationsTable({
|
||||
render: (text, record) => (
|
||||
<strong
|
||||
style={{
|
||||
color: record.difference > 0 ? "green" : "red",
|
||||
color: record.difference >= 0 ? "green" : "red",
|
||||
}}
|
||||
>
|
||||
{record.difference}
|
||||
|
||||
@@ -31,55 +31,19 @@ export default function OwnerDetailFormComponent({ form, loading }) {
|
||||
</LayoutFormRow>
|
||||
|
||||
<LayoutFormRow header={t("owners.forms.address")}>
|
||||
<Form.Item
|
||||
label={t("owners.fields.ownr_addr1")}
|
||||
name="ownr_addr1"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Form.Item label={t("owners.fields.ownr_addr1")} name="ownr_addr1">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("owners.fields.ownr_addr2")} name="ownr_addr2">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("owners.fields.ownr_city")}
|
||||
name="ownr_city"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Form.Item label={t("owners.fields.ownr_city")} name="ownr_city">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("owners.fields.ownr_st")}
|
||||
name="ownr_st"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Form.Item label={t("owners.fields.ownr_st")} name="ownr_st">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("owners.fields.ownr_zip")}
|
||||
name="ownr_zip"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Form.Item label={t("owners.fields.ownr_zip")} name="ownr_zip">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("owners.fields.ownr_ctry")} name="ownr_ctry">
|
||||
|
||||
@@ -82,7 +82,31 @@ export function PhonebookFormComponent({
|
||||
<Form.Item label={t("phonebook.fields.lastname")} name="lastname">
|
||||
<Input disabled={hasNoAccess} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("phonebook.fields.company")} name="company">
|
||||
<Form.Item
|
||||
label={t("phonebook.fields.company")}
|
||||
name="company"
|
||||
dependencies={["firstname", "lastname"]}
|
||||
rules={[
|
||||
({ getFieldsValue }) => ({
|
||||
validator(rule, value) {
|
||||
const { firstname, lastname, company } = getFieldsValue([
|
||||
"firstname",
|
||||
"lastname",
|
||||
"company",
|
||||
]);
|
||||
|
||||
if (
|
||||
(firstname && firstname.trim() !== "") ||
|
||||
(lastname && lastname.trim() !== "") ||
|
||||
(company && company.trim() !== "")
|
||||
) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(t("phonebook.labels.onenamerequired"));
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input disabled={hasNoAccess} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
|
||||
@@ -225,8 +225,10 @@ const r = ({ technician, state }) => {
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "totalhours" && state.sortedInfo.order,
|
||||
render: (text, record) =>
|
||||
record.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
record.larhrs.aggregate.sum.mod_lb_hrs,
|
||||
(
|
||||
record.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
record.larhrs.aggregate.sum.mod_lb_hrs
|
||||
).toFixed(1),
|
||||
},
|
||||
{
|
||||
title: i18n.t("production.labels.alert"),
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
import moment from "moment";
|
||||
import React, { useMemo } from "react";
|
||||
export default function ProductionlistColumnTouchTime({ job }) {
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ProductionlistColumnTouchTime);
|
||||
|
||||
export function ProductionlistColumnTouchTime({ bodyshop, job }) {
|
||||
let ct = useMemo(() => {
|
||||
if (!!job.actual_in) {
|
||||
const totalHrs =
|
||||
@@ -18,5 +32,7 @@ export default function ProductionlistColumnTouchTime({ job }) {
|
||||
return 0;
|
||||
}, [job]);
|
||||
|
||||
return <span>{ct}</span>;
|
||||
const underTarget = ct < bodyshop.target_touchtime;
|
||||
|
||||
return <span style={{ color: underTarget && "tomato" }}>{ct}</span>;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import PhoneFormItem, {
|
||||
} from "../form-items-formatted/phone-form-item.component";
|
||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
export default function ShopInfoGeneral({ form }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
@@ -425,6 +425,18 @@ export default function ShopInfoGeneral({ form }) {
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.jc_hourly_rates.mapa")}
|
||||
name={["jc_hourly_rates", "mapa"]}
|
||||
>
|
||||
<CurrencyInput />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.jc_hourly_rates.mash")}
|
||||
name={["jc_hourly_rates", "mash"]}
|
||||
>
|
||||
<CurrencyInput />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.labels.messagingpresets")}>
|
||||
<Form.List name={["md_messaging_presets"]}>
|
||||
|
||||
@@ -76,28 +76,10 @@ export default function VehicleDetailFormComponent({ form, loading }) {
|
||||
</LayoutFormRow>
|
||||
|
||||
<LayoutFormRow header={t("vehicles.forms.registration")}>
|
||||
<Form.Item
|
||||
label={t("vehicles.fields.plate_st")}
|
||||
name="plate_st"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Form.Item label={t("vehicles.fields.plate_st")} name="plate_st">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("vehicles.fields.plate_no")}
|
||||
name="plate_no"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Form.Item label={t("vehicles.fields.plate_no")} name="plate_no">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
|
||||
@@ -39,7 +39,7 @@ function VehicleDetailFormContainer({ vehicle, refetch }) {
|
||||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
title={t("menus.header.owners")}
|
||||
title={t("menus.header.vehicles")}
|
||||
extra={
|
||||
<Button
|
||||
type="primary"
|
||||
|
||||
@@ -85,6 +85,7 @@ export const INSERT_APPOINTMENT = gql`
|
||||
title
|
||||
isintake
|
||||
block
|
||||
color
|
||||
}
|
||||
}
|
||||
update_jobs(
|
||||
@@ -110,6 +111,7 @@ export const QUERY_APPOINTMENT_BY_DATE = gql`
|
||||
title
|
||||
isintake
|
||||
block
|
||||
color
|
||||
job {
|
||||
alt_transport
|
||||
ro_number
|
||||
|
||||
@@ -87,6 +87,7 @@ export const QUERY_BODYSHOP = gql`
|
||||
md_ccc_rates
|
||||
enforce_referral
|
||||
website
|
||||
jc_hourly_rates
|
||||
employees {
|
||||
id
|
||||
active
|
||||
@@ -171,6 +172,7 @@ export const UPDATE_SHOP = gql`
|
||||
md_ccc_rates
|
||||
enforce_referral
|
||||
website
|
||||
jc_hourly_rates
|
||||
employees {
|
||||
id
|
||||
first_name
|
||||
@@ -197,6 +199,28 @@ export const QUERY_INTAKE_CHECKLIST = gql`
|
||||
scheduled_delivery
|
||||
intakechecklist
|
||||
status
|
||||
labhrs: joblines_aggregate(
|
||||
where: {
|
||||
_and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }]
|
||||
}
|
||||
) {
|
||||
aggregate {
|
||||
sum {
|
||||
mod_lb_hrs
|
||||
}
|
||||
}
|
||||
}
|
||||
larhrs: joblines_aggregate(
|
||||
where: {
|
||||
_and: [{ mod_lbr_ty: { _eq: "LAR" } }, { removed: { _eq: false } }]
|
||||
}
|
||||
) {
|
||||
aggregate {
|
||||
sum {
|
||||
mod_lb_hrs
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -2,6 +2,10 @@ import { gql } from "@apollo/client";
|
||||
|
||||
export const GET_DOCUMENTS_BY_JOB = gql`
|
||||
query GET_DOCUMENTS_BY_JOB($jobId: uuid!) {
|
||||
jobs_by_pk(id: $jobId) {
|
||||
id
|
||||
ro_number
|
||||
}
|
||||
documents_aggregate(where: { jobid: { _eq: $jobId } }) {
|
||||
aggregate {
|
||||
sum {
|
||||
@@ -9,15 +13,14 @@ export const GET_DOCUMENTS_BY_JOB = gql`
|
||||
}
|
||||
}
|
||||
}
|
||||
documents(
|
||||
order_by: { updated_at: desc }
|
||||
where: { jobid: { _eq: $jobId } }
|
||||
) {
|
||||
documents(order_by: { takenat: desc }, where: { jobid: { _eq: $jobId } }) {
|
||||
id
|
||||
name
|
||||
key
|
||||
type
|
||||
size
|
||||
takenat
|
||||
extension
|
||||
bill {
|
||||
id
|
||||
invoice_number
|
||||
@@ -51,6 +54,7 @@ export const INSERT_NEW_DOCUMENT = gql`
|
||||
name
|
||||
key
|
||||
size
|
||||
takenat
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,7 +74,7 @@ export const QUERY_TEMPORARY_DOCS = gql`
|
||||
query QUERY_TEMPORARY_DOCS {
|
||||
documents(
|
||||
where: { jobid: { _is_null: true } }
|
||||
order_by: { updated_at: desc }
|
||||
order_by: { takenat: desc }
|
||||
) {
|
||||
id
|
||||
name
|
||||
@@ -78,6 +82,7 @@ export const QUERY_TEMPORARY_DOCS = gql`
|
||||
type
|
||||
extension
|
||||
size
|
||||
takenat
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -94,6 +99,7 @@ export const UPDATE_DOCUMENT = gql`
|
||||
type
|
||||
key
|
||||
size
|
||||
takenat
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -528,6 +528,7 @@ export const GET_JOB_BY_PK = gql`
|
||||
tax_part
|
||||
db_ref
|
||||
manual_line
|
||||
prt_dsmk_p
|
||||
billlines(limit: 1, order_by: { bill: { date: desc } }) {
|
||||
id
|
||||
quantity
|
||||
@@ -1484,6 +1485,7 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
|
||||
op_code_desc
|
||||
profitcenter_labor
|
||||
profitcenter_part
|
||||
prt_dsmk_p
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,6 +155,13 @@ function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
|
||||
layout="vertical"
|
||||
autoComplete={"off"}
|
||||
initialValues={{
|
||||
tax_tow_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
|
||||
tax_str_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
|
||||
tax_paint_mat_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
|
||||
tax_shop_mat_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
|
||||
tax_sub_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
|
||||
tax_lbr_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
|
||||
tax_levies_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
|
||||
parts_tax_rates: {
|
||||
PAA: {
|
||||
prt_type: "PAA",
|
||||
@@ -220,6 +227,30 @@ function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
|
||||
prt_tax_in: true,
|
||||
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
|
||||
},
|
||||
PASL: {
|
||||
prt_type: "PASL",
|
||||
prt_discp: 0,
|
||||
prt_mktyp: false,
|
||||
prt_mkupp: 0,
|
||||
prt_tax_in: true,
|
||||
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
|
||||
},
|
||||
PAP: {
|
||||
prt_type: "PAP",
|
||||
prt_discp: 0,
|
||||
prt_mktyp: false,
|
||||
prt_mkupp: 0,
|
||||
prt_tax_in: true,
|
||||
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
|
||||
},
|
||||
PAO: {
|
||||
prt_type: "PAO",
|
||||
prt_discp: 0,
|
||||
prt_mktyp: false,
|
||||
prt_mkupp: 0,
|
||||
prt_tax_in: true,
|
||||
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -385,7 +385,7 @@ export function Manage({ match, conflict, bodyshop }) {
|
||||
<Footer>
|
||||
<div style={{ textAlign: "center", margin: "1rem 0rem" }}>
|
||||
<div>
|
||||
{`ImEX Online V.${process.env.NODE_ENV} - ${
|
||||
{`ImEX Online ${
|
||||
process.env.REACT_APP_GIT_SHA
|
||||
} - ${preval`module.exports = new Date().toLocaleString("en-US", {timeZone: "America/Los_Angeles"});`}`}
|
||||
</div>
|
||||
|
||||
@@ -48,3 +48,8 @@ export const setPartnerVersion = (version) => ({
|
||||
type: ApplicationActionTypes.SET_PARTNER_VERSION,
|
||||
payload: version,
|
||||
});
|
||||
|
||||
export const setOnline = (isOnline) => ({
|
||||
type: ApplicationActionTypes.SET_ONLINE_STATUS,
|
||||
payload: isOnline,
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import ApplicationActionTypes from "./application.types";
|
||||
|
||||
const INITIAL_STATE = {
|
||||
loading: false,
|
||||
online: true,
|
||||
breadcrumbs: [],
|
||||
recentItems: [],
|
||||
selectedHeader: "home",
|
||||
@@ -21,6 +22,11 @@ const applicationReducer = (state = INITIAL_STATE, action) => {
|
||||
...state,
|
||||
selectedHeader: action.payload,
|
||||
};
|
||||
case ApplicationActionTypes.SET_ONLINE_STATUS:
|
||||
return {
|
||||
...state,
|
||||
online: action.payload,
|
||||
};
|
||||
case ApplicationActionTypes.ADD_RECENT_ITEM:
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -40,3 +40,7 @@ export const selectJobReadOnly = createSelector(
|
||||
[selectApplication],
|
||||
(application) => application.jobReadOnly
|
||||
);
|
||||
export const selectOnline = createSelector(
|
||||
[selectApplication],
|
||||
(application) => application.online
|
||||
);
|
||||
|
||||
@@ -9,5 +9,6 @@ const ApplicationActionTypes = {
|
||||
SET_SELECTED_HEADER: "SET_SELECTED_HEADER",
|
||||
SET_JOB_READONLY: "SET_JOB_READONLY",
|
||||
SET_PARTNER_VERSION: "SET_PARTNER_VERSION",
|
||||
SET_ONLINE_STATUS: "SET_ONLINE_STATUS",
|
||||
};
|
||||
export default ApplicationActionTypes;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import Fingerprint2 from "@fingerprintjs/fingerprintjs";
|
||||
import { notification } from "antd";
|
||||
import i18next from "i18next";
|
||||
import LogRocket from "logrocket";
|
||||
import { all, call, delay, put, select, takeLatest } from "redux-saga/effects";
|
||||
import {
|
||||
@@ -100,6 +102,10 @@ export function* updateUserDetails(userDetails) {
|
||||
try {
|
||||
yield updateCurrentUser(userDetails.payload);
|
||||
yield put(updateUserDetailsSuccess(userDetails.payload));
|
||||
notification.open({
|
||||
type: "success",
|
||||
message: i18next.t("profile.successes.updated"),
|
||||
});
|
||||
} catch (error) {
|
||||
//yield put(signOutFailure(error.message));
|
||||
}
|
||||
|
||||
@@ -218,6 +218,10 @@
|
||||
"invoice_federal_tax_rate": "Invoices - Federal Tax Rate",
|
||||
"invoice_local_tax_rate": "Invoices - Local Tax Rate",
|
||||
"invoice_state_tax_rate": "Invoices - Provincial/State Tax Rate",
|
||||
"jc_hourly_rates": {
|
||||
"mapa": "Job Costing - Paint Materials Hourly Cost Rate",
|
||||
"mash": "Job Costing - Shop Materials Hourly Cost Rate"
|
||||
},
|
||||
"lastnumberworkingdays": "Scoreboard - Last Number of Working Days",
|
||||
"logo_img_path": "Shop Logo",
|
||||
"logo_img_path_height": "Logo Image Height",
|
||||
@@ -555,7 +559,7 @@
|
||||
"driverinformation": "Driver's Information",
|
||||
"findcontract": "Find Contract",
|
||||
"findermodal": "Contract Finder",
|
||||
"noteconvertedfrom": "R.O. created from converted Courtesy Car Contract {{agreementnumber}}.",
|
||||
"noteconvertedfrom": "R.O. created from converted Courtesy Car Contract {{agreementnumber}}.",
|
||||
"populatefromjob": "Populate from Job",
|
||||
"rates": "Contract Rates",
|
||||
"time": "Time",
|
||||
@@ -683,7 +687,8 @@
|
||||
"deleting_cloudinary": "Error deleting document from storage. {{message}}",
|
||||
"getpresignurl": "Error obtaining presigned URL for document. {{message}}",
|
||||
"insert": "Unable to upload file. {{message}}",
|
||||
"nodocuments": "There are no documents."
|
||||
"nodocuments": "There are no documents.",
|
||||
"updating": "Error updating document. {{error}}"
|
||||
},
|
||||
"labels": {
|
||||
"confirmdelete": "Are you sure you want to delete these documents. This CANNOT be undone.",
|
||||
@@ -820,6 +825,8 @@
|
||||
"monday": "Monday",
|
||||
"na": "N/A",
|
||||
"no": "No",
|
||||
"nointernet": "It looks like you're not connected to the internet.",
|
||||
"nointernet_sub": "Please check your connection and try again. ",
|
||||
"none": "None",
|
||||
"out": "Out",
|
||||
"password": "Password",
|
||||
@@ -1226,6 +1233,8 @@
|
||||
"rate": "PVRT Rate"
|
||||
},
|
||||
"ca_gst_all_if_null": "If the job is marked as a \"GST Registrant\" and this value is set to $0, the customer will be responsible for paying all of the GST by default. ",
|
||||
"calc_repair_days": "Calculated Repair Days",
|
||||
"calc_repair_days_tt": "This is the approximate number of days required to complete the repair according to the target touch time in your shop configuration (current set to {{target_touchtime}}).",
|
||||
"cards": {
|
||||
"customer": "Customer Information",
|
||||
"damage": "Area of Damage",
|
||||
@@ -1279,7 +1288,7 @@
|
||||
"gppercent": "% G.P.",
|
||||
"hrs_claimed": "Hours Claimed",
|
||||
"hrs_total": "Hours Total",
|
||||
"importnote": "The job was initially imported on {{date}} at {{time}}.",
|
||||
"importnote": "The job was initially imported.",
|
||||
"inproduction": "In Production",
|
||||
"intakechecklist": "Intake Checklist",
|
||||
"job": "Job Details",
|
||||
@@ -1314,6 +1323,7 @@
|
||||
"partstotal": "This is the total of all parts and sublet amounts on the vehicle (some of these may require an in-house invoice).<br/>\nItems such as shop and paint materials, labor online lines, etc. are not included in this total.",
|
||||
"totalreturns": "The total amount of returns created for this job."
|
||||
},
|
||||
"prt_dsmk_total": "Line Item Markup",
|
||||
"rates": "Rates",
|
||||
"rates_subtotal": "All Rates Subtotal",
|
||||
"reconciliation": {
|
||||
@@ -1339,19 +1349,19 @@
|
||||
"state_tax_amt": "Provincial/State Taxes",
|
||||
"subletstotal": "Sublets Total",
|
||||
"subtotal": "Subtotal",
|
||||
"supplementnote": "The job had a supplement imported on {{date}} at {{time}}.",
|
||||
"supplementnote": "The job had a supplement imported.",
|
||||
"suspense": "Suspense",
|
||||
"total_cost": "Total Cost",
|
||||
"total_cust_payable": "Total Customer Amount Payable",
|
||||
"total_repairs": "Total Repairs",
|
||||
"total_sales": "Total Sales",
|
||||
"totals": "Totals",
|
||||
"unvoidnote": "This job was unvoided by {{email}}.",
|
||||
"unvoidnote": "This job was unvoided.",
|
||||
"vehicle_info": "Vehicle",
|
||||
"vehicleassociation": "Vehicle Association",
|
||||
"viewallocations": "View Allocations",
|
||||
"voidjob": "Are you sure you want to void this job? This cannot be easily undone. ",
|
||||
"voidnote": "This repair order was voided on {{date}} at {{time}}."
|
||||
"voidnote": "This job was voided."
|
||||
},
|
||||
"successes": {
|
||||
"addedtoproduction": "Job added to production board.",
|
||||
@@ -1662,6 +1672,8 @@
|
||||
"state": "Province/State"
|
||||
},
|
||||
"labels": {
|
||||
"noneselected": "No phone book entry selected. ",
|
||||
"onenamerequired": "At least one name related field is required.",
|
||||
"vendorcategory": "Vendor"
|
||||
},
|
||||
"successes": {
|
||||
@@ -1813,6 +1825,9 @@
|
||||
},
|
||||
"labels": {
|
||||
"activeshop": "Active Shop"
|
||||
},
|
||||
"successes": {
|
||||
"updated": "Profile updated successfully."
|
||||
}
|
||||
},
|
||||
"reportcenter": {
|
||||
|
||||
@@ -218,6 +218,10 @@
|
||||
"invoice_federal_tax_rate": "",
|
||||
"invoice_local_tax_rate": "",
|
||||
"invoice_state_tax_rate": "",
|
||||
"jc_hourly_rates": {
|
||||
"mapa": "",
|
||||
"mash": ""
|
||||
},
|
||||
"lastnumberworkingdays": "",
|
||||
"logo_img_path": "",
|
||||
"logo_img_path_height": "",
|
||||
@@ -683,7 +687,8 @@
|
||||
"deleting_cloudinary": "",
|
||||
"getpresignurl": "Error al obtener la URL prescrita para el documento. {{message}}",
|
||||
"insert": "Incapaz de cargar el archivo. {{message}}",
|
||||
"nodocuments": "No hay documentos"
|
||||
"nodocuments": "No hay documentos",
|
||||
"updating": ""
|
||||
},
|
||||
"labels": {
|
||||
"confirmdelete": "",
|
||||
@@ -820,6 +825,8 @@
|
||||
"monday": "",
|
||||
"na": "N / A",
|
||||
"no": "",
|
||||
"nointernet": "",
|
||||
"nointernet_sub": "",
|
||||
"none": "",
|
||||
"out": "Afuera",
|
||||
"password": "",
|
||||
@@ -1226,6 +1233,8 @@
|
||||
"rate": ""
|
||||
},
|
||||
"ca_gst_all_if_null": "",
|
||||
"calc_repair_days": "",
|
||||
"calc_repair_days_tt": "",
|
||||
"cards": {
|
||||
"customer": "Información al cliente",
|
||||
"damage": "Área de Daño",
|
||||
@@ -1314,6 +1323,7 @@
|
||||
"partstotal": "",
|
||||
"totalreturns": ""
|
||||
},
|
||||
"prt_dsmk_total": "",
|
||||
"rates": "Tarifas",
|
||||
"rates_subtotal": "",
|
||||
"reconciliation": {
|
||||
@@ -1662,6 +1672,8 @@
|
||||
"state": ""
|
||||
},
|
||||
"labels": {
|
||||
"noneselected": "",
|
||||
"onenamerequired": "",
|
||||
"vendorcategory": ""
|
||||
},
|
||||
"successes": {
|
||||
@@ -1813,6 +1825,9 @@
|
||||
},
|
||||
"labels": {
|
||||
"activeshop": ""
|
||||
},
|
||||
"successes": {
|
||||
"updated": ""
|
||||
}
|
||||
},
|
||||
"reportcenter": {
|
||||
|
||||
@@ -218,6 +218,10 @@
|
||||
"invoice_federal_tax_rate": "",
|
||||
"invoice_local_tax_rate": "",
|
||||
"invoice_state_tax_rate": "",
|
||||
"jc_hourly_rates": {
|
||||
"mapa": "",
|
||||
"mash": ""
|
||||
},
|
||||
"lastnumberworkingdays": "",
|
||||
"logo_img_path": "",
|
||||
"logo_img_path_height": "",
|
||||
@@ -683,7 +687,8 @@
|
||||
"deleting_cloudinary": "",
|
||||
"getpresignurl": "Erreur lors de l'obtention de l'URL présignée pour le document. {{message}}",
|
||||
"insert": "Incapable de télécharger le fichier. {{message}}",
|
||||
"nodocuments": "Il n'y a pas de documents."
|
||||
"nodocuments": "Il n'y a pas de documents.",
|
||||
"updating": ""
|
||||
},
|
||||
"labels": {
|
||||
"confirmdelete": "",
|
||||
@@ -820,6 +825,8 @@
|
||||
"monday": "",
|
||||
"na": "N / A",
|
||||
"no": "",
|
||||
"nointernet": "",
|
||||
"nointernet_sub": "",
|
||||
"none": "",
|
||||
"out": "En dehors",
|
||||
"password": "",
|
||||
@@ -1226,6 +1233,8 @@
|
||||
"rate": ""
|
||||
},
|
||||
"ca_gst_all_if_null": "",
|
||||
"calc_repair_days": "",
|
||||
"calc_repair_days_tt": "",
|
||||
"cards": {
|
||||
"customer": "Informations client",
|
||||
"damage": "Zone de dommages",
|
||||
@@ -1314,6 +1323,7 @@
|
||||
"partstotal": "",
|
||||
"totalreturns": ""
|
||||
},
|
||||
"prt_dsmk_total": "",
|
||||
"rates": "Les taux",
|
||||
"rates_subtotal": "",
|
||||
"reconciliation": {
|
||||
@@ -1662,6 +1672,8 @@
|
||||
"state": ""
|
||||
},
|
||||
"labels": {
|
||||
"noneselected": "",
|
||||
"onenamerequired": "",
|
||||
"vendorcategory": ""
|
||||
},
|
||||
"successes": {
|
||||
@@ -1813,6 +1825,9 @@
|
||||
},
|
||||
"labels": {
|
||||
"activeshop": ""
|
||||
},
|
||||
"successes": {
|
||||
"updated": ""
|
||||
}
|
||||
},
|
||||
"reportcenter": {
|
||||
|
||||
@@ -5696,6 +5696,11 @@ exenv@^1.2.2:
|
||||
resolved "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz"
|
||||
integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=
|
||||
|
||||
exifr@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/exifr/-/exifr-7.0.0.tgz#065d54639f2999ac901dd8475db99b4d3f31f11e"
|
||||
integrity sha512-5JRtopcolAfsX4YGcvTkldXyFDC+VPV40DxhSdRvrDr4OJQHtyccNoYl4aRzYBnVVpNueMmenS0KhzLiyNSfEQ==
|
||||
|
||||
exit@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."documents" DROP COLUMN "takenat";
|
||||
type: run_sql
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."documents" ADD COLUMN "takenat" timestamptz NULL;
|
||||
type: run_sql
|
||||
@@ -0,0 +1,46 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: documents
|
||||
schema: public
|
||||
type: drop_insert_permission
|
||||
- args:
|
||||
permission:
|
||||
check:
|
||||
_or:
|
||||
- job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
- bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
columns:
|
||||
- billid
|
||||
- bodyshopid
|
||||
- created_at
|
||||
- extension
|
||||
- id
|
||||
- jobid
|
||||
- key
|
||||
- name
|
||||
- size
|
||||
- type
|
||||
- updated_at
|
||||
- uploaded_by
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: documents
|
||||
schema: public
|
||||
type: create_insert_permission
|
||||
@@ -0,0 +1,47 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: documents
|
||||
schema: public
|
||||
type: drop_insert_permission
|
||||
- args:
|
||||
permission:
|
||||
check:
|
||||
_or:
|
||||
- job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
- bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
columns:
|
||||
- billid
|
||||
- bodyshopid
|
||||
- created_at
|
||||
- extension
|
||||
- id
|
||||
- jobid
|
||||
- key
|
||||
- name
|
||||
- size
|
||||
- takenat
|
||||
- type
|
||||
- updated_at
|
||||
- uploaded_by
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: documents
|
||||
schema: public
|
||||
type: create_insert_permission
|
||||
@@ -0,0 +1,47 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: documents
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: true
|
||||
columns:
|
||||
- billid
|
||||
- bodyshopid
|
||||
- created_at
|
||||
- extension
|
||||
- id
|
||||
- jobid
|
||||
- key
|
||||
- name
|
||||
- size
|
||||
- type
|
||||
- updated_at
|
||||
- uploaded_by
|
||||
computed_fields: []
|
||||
filter:
|
||||
_or:
|
||||
- job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
- bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
role: user
|
||||
table:
|
||||
name: documents
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -0,0 +1,48 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: documents
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: true
|
||||
columns:
|
||||
- billid
|
||||
- bodyshopid
|
||||
- created_at
|
||||
- extension
|
||||
- id
|
||||
- jobid
|
||||
- key
|
||||
- name
|
||||
- size
|
||||
- takenat
|
||||
- type
|
||||
- updated_at
|
||||
- uploaded_by
|
||||
computed_fields: []
|
||||
filter:
|
||||
_or:
|
||||
- job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
- bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
role: user
|
||||
table:
|
||||
name: documents
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -0,0 +1,6 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."bodyshops" ADD CONSTRAINT "bodyshops_autohouseid_key"
|
||||
UNIQUE ("autohouseid");
|
||||
type: run_sql
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."bodyshops" DROP CONSTRAINT "bodyshops_autohouseid_key";
|
||||
type: run_sql
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."bodyshops" DROP COLUMN "jc_hourly_rates";
|
||||
type: run_sql
|
||||
@@ -0,0 +1,6 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."bodyshops" ADD COLUMN "jc_hourly_rates" jsonb NULL
|
||||
DEFAULT jsonb_build_object();
|
||||
type: run_sql
|
||||
@@ -0,0 +1,83 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- accountingconfig
|
||||
- address1
|
||||
- address2
|
||||
- appt_alt_transport
|
||||
- appt_colors
|
||||
- appt_length
|
||||
- bill_tax_rates
|
||||
- city
|
||||
- country
|
||||
- created_at
|
||||
- default_adjustment_rate
|
||||
- deliverchecklist
|
||||
- email
|
||||
- enforce_class
|
||||
- enforce_referral
|
||||
- federal_tax_id
|
||||
- id
|
||||
- imexshopid
|
||||
- inhousevendorid
|
||||
- insurance_vendor_id
|
||||
- intakechecklist
|
||||
- jobsizelimit
|
||||
- logo_img_path
|
||||
- md_categories
|
||||
- md_ccc_rates
|
||||
- md_classes
|
||||
- md_hour_split
|
||||
- md_ins_cos
|
||||
- md_labor_rates
|
||||
- md_messaging_presets
|
||||
- md_notes_presets
|
||||
- md_order_statuses
|
||||
- md_parts_locations
|
||||
- md_payment_types
|
||||
- md_rbac
|
||||
- md_referral_sources
|
||||
- md_responsibility_centers
|
||||
- md_ro_statuses
|
||||
- messagingservicesid
|
||||
- phone
|
||||
- prodtargethrs
|
||||
- production_config
|
||||
- region_config
|
||||
- schedule_end_time
|
||||
- schedule_start_time
|
||||
- scoreboard_target
|
||||
- shopname
|
||||
- shoprates
|
||||
- speedprint
|
||||
- ssbuckets
|
||||
- state
|
||||
- state_tax_id
|
||||
- stripe_acct_id
|
||||
- sub_status
|
||||
- target_touchtime
|
||||
- template_header
|
||||
- textid
|
||||
- updated_at
|
||||
- use_fippa
|
||||
- website
|
||||
- workingdays
|
||||
- zip_post
|
||||
computed_fields: []
|
||||
filter:
|
||||
associations:
|
||||
user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -0,0 +1,84 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- accountingconfig
|
||||
- address1
|
||||
- address2
|
||||
- appt_alt_transport
|
||||
- appt_colors
|
||||
- appt_length
|
||||
- bill_tax_rates
|
||||
- city
|
||||
- country
|
||||
- created_at
|
||||
- default_adjustment_rate
|
||||
- deliverchecklist
|
||||
- email
|
||||
- enforce_class
|
||||
- enforce_referral
|
||||
- federal_tax_id
|
||||
- id
|
||||
- imexshopid
|
||||
- inhousevendorid
|
||||
- insurance_vendor_id
|
||||
- intakechecklist
|
||||
- jc_hourly_rates
|
||||
- jobsizelimit
|
||||
- logo_img_path
|
||||
- md_categories
|
||||
- md_ccc_rates
|
||||
- md_classes
|
||||
- md_hour_split
|
||||
- md_ins_cos
|
||||
- md_labor_rates
|
||||
- md_messaging_presets
|
||||
- md_notes_presets
|
||||
- md_order_statuses
|
||||
- md_parts_locations
|
||||
- md_payment_types
|
||||
- md_rbac
|
||||
- md_referral_sources
|
||||
- md_responsibility_centers
|
||||
- md_ro_statuses
|
||||
- messagingservicesid
|
||||
- phone
|
||||
- prodtargethrs
|
||||
- production_config
|
||||
- region_config
|
||||
- schedule_end_time
|
||||
- schedule_start_time
|
||||
- scoreboard_target
|
||||
- shopname
|
||||
- shoprates
|
||||
- speedprint
|
||||
- ssbuckets
|
||||
- state
|
||||
- state_tax_id
|
||||
- stripe_acct_id
|
||||
- sub_status
|
||||
- target_touchtime
|
||||
- template_header
|
||||
- textid
|
||||
- updated_at
|
||||
- use_fippa
|
||||
- website
|
||||
- workingdays
|
||||
- zip_post
|
||||
computed_fields: []
|
||||
filter:
|
||||
associations:
|
||||
user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -0,0 +1,77 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: drop_update_permission
|
||||
- args:
|
||||
permission:
|
||||
columns:
|
||||
- accountingconfig
|
||||
- address1
|
||||
- address2
|
||||
- appt_alt_transport
|
||||
- appt_colors
|
||||
- appt_length
|
||||
- bill_tax_rates
|
||||
- city
|
||||
- country
|
||||
- created_at
|
||||
- default_adjustment_rate
|
||||
- deliverchecklist
|
||||
- email
|
||||
- enforce_class
|
||||
- enforce_referral
|
||||
- federal_tax_id
|
||||
- id
|
||||
- inhousevendorid
|
||||
- insurance_vendor_id
|
||||
- intakechecklist
|
||||
- logo_img_path
|
||||
- md_categories
|
||||
- md_ccc_rates
|
||||
- md_classes
|
||||
- md_hour_split
|
||||
- md_ins_cos
|
||||
- md_labor_rates
|
||||
- md_messaging_presets
|
||||
- md_notes_presets
|
||||
- md_order_statuses
|
||||
- md_parts_locations
|
||||
- md_payment_types
|
||||
- md_rbac
|
||||
- md_referral_sources
|
||||
- md_responsibility_centers
|
||||
- md_ro_statuses
|
||||
- phone
|
||||
- prodtargethrs
|
||||
- production_config
|
||||
- schedule_end_time
|
||||
- schedule_start_time
|
||||
- scoreboard_target
|
||||
- shopname
|
||||
- shoprates
|
||||
- speedprint
|
||||
- ssbuckets
|
||||
- state
|
||||
- state_tax_id
|
||||
- target_touchtime
|
||||
- updated_at
|
||||
- use_fippa
|
||||
- website
|
||||
- workingdays
|
||||
- zip_post
|
||||
filter:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: create_update_permission
|
||||
@@ -0,0 +1,78 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: drop_update_permission
|
||||
- args:
|
||||
permission:
|
||||
columns:
|
||||
- accountingconfig
|
||||
- address1
|
||||
- address2
|
||||
- appt_alt_transport
|
||||
- appt_colors
|
||||
- appt_length
|
||||
- bill_tax_rates
|
||||
- city
|
||||
- country
|
||||
- created_at
|
||||
- default_adjustment_rate
|
||||
- deliverchecklist
|
||||
- email
|
||||
- enforce_class
|
||||
- enforce_referral
|
||||
- federal_tax_id
|
||||
- id
|
||||
- inhousevendorid
|
||||
- insurance_vendor_id
|
||||
- intakechecklist
|
||||
- jc_hourly_rates
|
||||
- logo_img_path
|
||||
- md_categories
|
||||
- md_ccc_rates
|
||||
- md_classes
|
||||
- md_hour_split
|
||||
- md_ins_cos
|
||||
- md_labor_rates
|
||||
- md_messaging_presets
|
||||
- md_notes_presets
|
||||
- md_order_statuses
|
||||
- md_parts_locations
|
||||
- md_payment_types
|
||||
- md_rbac
|
||||
- md_referral_sources
|
||||
- md_responsibility_centers
|
||||
- md_ro_statuses
|
||||
- phone
|
||||
- prodtargethrs
|
||||
- production_config
|
||||
- schedule_end_time
|
||||
- schedule_start_time
|
||||
- scoreboard_target
|
||||
- shopname
|
||||
- shoprates
|
||||
- speedprint
|
||||
- ssbuckets
|
||||
- state
|
||||
- state_tax_id
|
||||
- target_touchtime
|
||||
- updated_at
|
||||
- use_fippa
|
||||
- website
|
||||
- workingdays
|
||||
- zip_post
|
||||
filter:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: create_update_permission
|
||||
@@ -771,6 +771,7 @@ tables:
|
||||
- inhousevendorid
|
||||
- insurance_vendor_id
|
||||
- intakechecklist
|
||||
- jc_hourly_rates
|
||||
- jobsizelimit
|
||||
- logo_img_path
|
||||
- md_categories
|
||||
@@ -841,6 +842,7 @@ tables:
|
||||
- inhousevendorid
|
||||
- insurance_vendor_id
|
||||
- intakechecklist
|
||||
- jc_hourly_rates
|
||||
- logo_img_path
|
||||
- md_categories
|
||||
- md_ccc_rates
|
||||
@@ -1520,6 +1522,7 @@ tables:
|
||||
- key
|
||||
- name
|
||||
- size
|
||||
- takenat
|
||||
- type
|
||||
- updated_at
|
||||
- uploaded_by
|
||||
@@ -1536,6 +1539,7 @@ tables:
|
||||
- key
|
||||
- name
|
||||
- size
|
||||
- takenat
|
||||
- type
|
||||
- updated_at
|
||||
- uploaded_by
|
||||
|
||||
@@ -34,7 +34,11 @@ app.post("/sendemail", fb.validateFirebaseIdToken, sendEmail.sendEmail);
|
||||
//Test route to ensure Express is responding.
|
||||
app.get("/test", async function (req, res) {
|
||||
console.log("Incoming request verified.", req);
|
||||
res.status(200).send("OK");
|
||||
const Commit = require("child_process").execSync(
|
||||
"git rev-parse --short HEAD"
|
||||
);
|
||||
|
||||
res.status(200).send(`OK - ${Commit}`);
|
||||
});
|
||||
const test = require("./server/_test/test.js");
|
||||
app.post("/test", test.testResponse);
|
||||
|
||||
@@ -203,7 +203,8 @@ const generateInvoiceQbxml = (
|
||||
//Create the invoice lines mapping.
|
||||
jobs_by_pk.joblines.map((jobline) => {
|
||||
//Parts Lines
|
||||
if (jobline.db_ref === "936008") { //If either of these DB REFs change, they also need to change in job-totals calculations.
|
||||
if (jobline.db_ref === "936008") {
|
||||
//If either of these DB REFs change, they also need to change in job-totals calculations.
|
||||
hasMapaLine = true;
|
||||
}
|
||||
if (jobline.db_ref === "936007") {
|
||||
@@ -211,9 +212,16 @@ const generateInvoiceQbxml = (
|
||||
}
|
||||
|
||||
if (jobline.profitcenter_part && jobline.act_price) {
|
||||
const DineroAmount = Dinero({
|
||||
let DineroAmount = Dinero({
|
||||
amount: Math.round(jobline.act_price * 100),
|
||||
}).multiply(jobline.part_qty || 1);
|
||||
|
||||
if (jobline.prt_dsmk_p && jobline.prt_dsmk_p !== 0) {
|
||||
console.log("Have a part discount", jobline);
|
||||
DineroAmount = DineroAmount.add(
|
||||
DineroAmount.percentage(jobline.prt_dsmk_p || 0)
|
||||
);
|
||||
}
|
||||
const account = responsibilityCenters.profits.find(
|
||||
(i) => jobline.profitcenter_part.toLowerCase() === i.name.toLowerCase()
|
||||
);
|
||||
|
||||
@@ -101,6 +101,7 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
|
||||
profitcenter_labor
|
||||
profitcenter_part
|
||||
db_ref
|
||||
prt_dsmk_p
|
||||
}
|
||||
}
|
||||
bodyshops(where: {associations: {active: {_eq: true}}}) {
|
||||
@@ -566,6 +567,7 @@ exports.GET_JOB_BY_PK = ` query GET_JOB_BY_PK($id: uuid!) {
|
||||
tax_part
|
||||
db_ref
|
||||
manual_line
|
||||
prt_dsmk_p
|
||||
parts_order_lines {
|
||||
id
|
||||
parts_order {
|
||||
@@ -679,6 +681,7 @@ exports.QUERY_JOB_COSTING_DETAILS = ` query QUERY_JOB_COSTING_DETAILS($id: uuid!
|
||||
bodyshop{
|
||||
id
|
||||
md_responsibility_centers
|
||||
jc_hourly_rates
|
||||
}
|
||||
}
|
||||
}`;
|
||||
@@ -779,6 +782,7 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT
|
||||
bodyshop {
|
||||
id
|
||||
md_responsibility_centers
|
||||
jc_hourly_rates
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,7 @@ Dinero.globalRoundingMode = "HALF_EVEN";
|
||||
|
||||
async function JobCosting(req, res) {
|
||||
const { jobid } = req.body;
|
||||
console.log("🚀 ~ file: job-costing.js ~ line 13 ~ jobid", jobid);
|
||||
console.time("querydata");
|
||||
console.time("Query for Data");
|
||||
const BearerToken = req.headers.authorization;
|
||||
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
||||
@@ -122,27 +121,34 @@ async function JobCostingMulti(req, res) {
|
||||
});
|
||||
|
||||
//Add all summary data.
|
||||
multiSummary.summaryData.totalPartsSales = multiSummary.summaryData.totalPartsSales.add(
|
||||
costingData.summaryData.totalPartsSales
|
||||
);
|
||||
multiSummary.summaryData.totalSales = multiSummary.summaryData.totalSales.add(
|
||||
costingData.summaryData.totalSales
|
||||
);
|
||||
multiSummary.summaryData.totalLaborCost = multiSummary.summaryData.totalLaborCost.add(
|
||||
costingData.summaryData.totalLaborCost
|
||||
);
|
||||
multiSummary.summaryData.totalLaborSales = multiSummary.summaryData.totalLaborSales.add(
|
||||
costingData.summaryData.totalLaborSales
|
||||
);
|
||||
multiSummary.summaryData.totalPartsCost = multiSummary.summaryData.totalPartsCost.add(
|
||||
costingData.summaryData.totalPartsCost
|
||||
);
|
||||
multiSummary.summaryData.totalCost = multiSummary.summaryData.totalCost.add(
|
||||
costingData.summaryData.totalCost
|
||||
);
|
||||
multiSummary.summaryData.gpdollars = multiSummary.summaryData.gpdollars.add(
|
||||
costingData.summaryData.gpdollars
|
||||
);
|
||||
multiSummary.summaryData.totalPartsSales =
|
||||
multiSummary.summaryData.totalPartsSales.add(
|
||||
costingData.summaryData.totalPartsSales
|
||||
);
|
||||
multiSummary.summaryData.totalSales =
|
||||
multiSummary.summaryData.totalSales.add(
|
||||
costingData.summaryData.totalSales
|
||||
);
|
||||
multiSummary.summaryData.totalLaborCost =
|
||||
multiSummary.summaryData.totalLaborCost.add(
|
||||
costingData.summaryData.totalLaborCost
|
||||
);
|
||||
multiSummary.summaryData.totalLaborSales =
|
||||
multiSummary.summaryData.totalLaborSales.add(
|
||||
costingData.summaryData.totalLaborSales
|
||||
);
|
||||
multiSummary.summaryData.totalPartsCost =
|
||||
multiSummary.summaryData.totalPartsCost.add(
|
||||
costingData.summaryData.totalPartsCost
|
||||
);
|
||||
multiSummary.summaryData.totalCost =
|
||||
multiSummary.summaryData.totalCost.add(
|
||||
costingData.summaryData.totalCost
|
||||
);
|
||||
multiSummary.summaryData.gpdollars =
|
||||
multiSummary.summaryData.gpdollars.add(
|
||||
costingData.summaryData.gpdollars
|
||||
);
|
||||
console.timeEnd(`SummaryOfCostingData-${job.id}`);
|
||||
//Take the summary data & add it to total summary data.
|
||||
});
|
||||
@@ -202,6 +208,8 @@ function GenerateCostingData(job) {
|
||||
job.bodyshop.md_responsibility_centers.costs.map((p) => p.name)
|
||||
);
|
||||
|
||||
const materialsHours = { mapaHrs: 0, mashHrs: 0 };
|
||||
|
||||
//Massage the data.
|
||||
const jobLineTotalsByProfitCenter =
|
||||
job &&
|
||||
@@ -220,14 +228,14 @@ function GenerateCostingData(job) {
|
||||
}).multiply(val.mod_lb_hrs || 0);
|
||||
if (!acc.labor[laborProfitCenter])
|
||||
acc.labor[laborProfitCenter] = Dinero();
|
||||
acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter].add(
|
||||
laborAmount
|
||||
);
|
||||
acc.labor[laborProfitCenter] =
|
||||
acc.labor[laborProfitCenter].add(laborAmount);
|
||||
|
||||
if (val.mod_lbr_ty === "LAR") {
|
||||
if (!acc.labor[defaultProfits["MAPA"]])
|
||||
acc.labor[defaultProfits["MAPA"]] = Dinero();
|
||||
|
||||
materialsHours.mapaHrs += val.mod_lb_hrs || 0;
|
||||
acc.labor[defaultProfits["MAPA"]] = acc.labor[
|
||||
defaultProfits["MAPA"]
|
||||
].add(
|
||||
@@ -247,6 +255,7 @@ function GenerateCostingData(job) {
|
||||
amount: Math.round((job.rate_mash || 0) * 100),
|
||||
}).multiply(val.mod_lb_hrs || 0)
|
||||
);
|
||||
materialsHours.mashHrs += val.mod_lb_hrs || 0;
|
||||
}
|
||||
//If labor line, add to paint and shop materials.
|
||||
}
|
||||
@@ -265,12 +274,19 @@ function GenerateCostingData(job) {
|
||||
);
|
||||
const partsAmount = Dinero({
|
||||
amount: Math.round((val.act_price || 0) * 100),
|
||||
}).multiply(val.part_qty || 1);
|
||||
})
|
||||
.multiply(val.part_qty || 1)
|
||||
.add(
|
||||
Dinero({
|
||||
amount: Math.round((val.act_price || 0) * 100),
|
||||
})
|
||||
.multiply(val.part_qty || 0)
|
||||
.percentage(val.prt_dsmk_p)
|
||||
);
|
||||
if (!acc.parts[partsProfitCenter])
|
||||
acc.parts[partsProfitCenter] = Dinero();
|
||||
acc.parts[partsProfitCenter] = acc.parts[partsProfitCenter].add(
|
||||
partsAmount
|
||||
);
|
||||
acc.parts[partsProfitCenter] =
|
||||
acc.parts[partsProfitCenter].add(partsAmount);
|
||||
}
|
||||
|
||||
//To deal with additional costs.
|
||||
@@ -287,18 +303,20 @@ function GenerateCostingData(job) {
|
||||
} else {
|
||||
const partsAmount = Dinero({
|
||||
amount: Math.round((val.act_price || 0) * 100),
|
||||
}).multiply(val.part_qty || 1);
|
||||
console.log(
|
||||
`*** partsAmount`,
|
||||
val.line_desc,
|
||||
partsProfitCenter,
|
||||
partsAmount.toJSON()
|
||||
);
|
||||
})
|
||||
.multiply(val.part_qty || 1)
|
||||
.add(
|
||||
Dinero({
|
||||
amount: Math.round((val.act_price || 0) * 100),
|
||||
})
|
||||
.multiply(val.part_qty || 0)
|
||||
.percentage(val.prt_dsmk_p)
|
||||
);
|
||||
|
||||
if (!acc.parts[partsProfitCenter])
|
||||
acc.parts[partsProfitCenter] = Dinero();
|
||||
acc.parts[partsProfitCenter] = acc.parts[partsProfitCenter].add(
|
||||
partsAmount
|
||||
);
|
||||
acc.parts[partsProfitCenter] =
|
||||
acc.parts[partsProfitCenter].add(partsAmount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,6 +346,52 @@ function GenerateCostingData(job) {
|
||||
return bill_acc;
|
||||
}, {});
|
||||
|
||||
//If the hourly rates for job costing are set, add them in.
|
||||
if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mapa) {
|
||||
if (
|
||||
!billTotalsByCostCenters[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
|
||||
]
|
||||
)
|
||||
billTotalsByCostCenters[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
|
||||
] = Dinero();
|
||||
billTotalsByCostCenters[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
|
||||
] = billTotalsByCostCenters[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
|
||||
].add(
|
||||
Dinero({
|
||||
amount:
|
||||
(job.bodyshop.jc_hourly_rates &&
|
||||
job.bodyshop.jc_hourly_rates.mapa * 100) ||
|
||||
0,
|
||||
}).multiply(materialsHours.mapaHrs)
|
||||
);
|
||||
}
|
||||
if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mash) {
|
||||
if (
|
||||
!billTotalsByCostCenters[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
|
||||
]
|
||||
)
|
||||
billTotalsByCostCenters[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
|
||||
] = Dinero();
|
||||
billTotalsByCostCenters[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
|
||||
] = billTotalsByCostCenters[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
|
||||
].add(
|
||||
Dinero({
|
||||
amount:
|
||||
(job.bodyshop.jc_hourly_rates &&
|
||||
job.bodyshop.jc_hourly_rates.mash * 100) ||
|
||||
0,
|
||||
}).multiply(materialsHours.mashHrs)
|
||||
);
|
||||
}
|
||||
|
||||
const ticketTotalsByCostCenter = job.timetickets.reduce(
|
||||
(ticket_acc, ticket_val) => {
|
||||
//At the invoice level.
|
||||
|
||||
@@ -86,6 +86,7 @@ async function Totals(req, res) {
|
||||
res.status(400).send(JSON.stringify(error));
|
||||
}
|
||||
}
|
||||
|
||||
function CalculateRatesTotals(ratesList) {
|
||||
const jobLines = ratesList.joblines.filter((jl) => !jl.removed);
|
||||
|
||||
@@ -211,6 +212,13 @@ function CalculatePartsTotals(jobLines) {
|
||||
...acc,
|
||||
parts: {
|
||||
...acc.parts,
|
||||
prt_dsmk_total: acc.parts.prt_dsmk_total.add(
|
||||
Dinero({
|
||||
amount: Math.round((value.act_price || 0) * 100),
|
||||
})
|
||||
.multiply(value.part_qty || 0)
|
||||
.percentage(value.prt_dsmk_p)
|
||||
),
|
||||
list: {
|
||||
...acc.parts.list,
|
||||
[value.part_type]:
|
||||
@@ -229,11 +237,19 @@ function CalculatePartsTotals(jobLines) {
|
||||
}).multiply(value.part_qty || 0),
|
||||
},
|
||||
},
|
||||
subtotal: acc.parts.subtotal.add(
|
||||
Dinero({
|
||||
amount: Math.round(value.act_price * 100),
|
||||
}).multiply(value.part_qty || 0)
|
||||
),
|
||||
subtotal: acc.parts.subtotal
|
||||
.add(
|
||||
Dinero({
|
||||
amount: Math.round(value.act_price * 100),
|
||||
}).multiply(value.part_qty || 0)
|
||||
)
|
||||
.add(
|
||||
Dinero({
|
||||
amount: Math.round((value.act_price || 0) * 100),
|
||||
})
|
||||
.multiply(value.part_qty || 0)
|
||||
.percentage(value.prt_dsmk_p)
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -241,6 +257,7 @@ function CalculatePartsTotals(jobLines) {
|
||||
{
|
||||
parts: {
|
||||
list: {},
|
||||
prt_dsmk_total: Dinero(),
|
||||
subtotal: Dinero({ amount: 0 }),
|
||||
total: Dinero({ amount: 0 }),
|
||||
},
|
||||
@@ -360,6 +377,13 @@ function CalculateTaxesTotals(job, otherTotals) {
|
||||
statePartsTax = statePartsTax.add(
|
||||
Dinero({ amount: Math.round((val.act_price || 0) * 100) })
|
||||
.multiply(val.part_qty || 1)
|
||||
.add(
|
||||
Dinero({
|
||||
amount: Math.round((val.act_price || 0) * 100),
|
||||
})
|
||||
.multiply(val.part_qty || 0)
|
||||
.percentage(val.prt_dsmk_p)
|
||||
)
|
||||
.percentage(
|
||||
((job.parts_tax_rates &&
|
||||
job.parts_tax_rates[val.part_type] &&
|
||||
|
||||
@@ -40,7 +40,9 @@ exports.renameKeys = async (req, res) => {
|
||||
try {
|
||||
const res = {
|
||||
id: d.id,
|
||||
...(await cloudinary.uploader.rename(d.from, d.to)),
|
||||
...(await cloudinary.uploader.rename(d.from, d.to, {
|
||||
resource_type: DetermineFileType(d.type),
|
||||
})),
|
||||
};
|
||||
return res;
|
||||
} catch (error) {
|
||||
@@ -56,3 +58,14 @@ exports.renameKeys = async (req, res) => {
|
||||
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
//Also needs to be updated in upload utility and mobile app.
|
||||
function DetermineFileType(filetype) {
|
||||
if (!filetype) return "auto";
|
||||
else if (filetype.startsWith("image")) return "image";
|
||||
else if (filetype.startsWith("video")) return "video";
|
||||
else if (filetype.startsWith("application/pdf")) return "image";
|
||||
else if (filetype.startsWith("application")) return "raw";
|
||||
|
||||
return "auto";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user