Merged in release/2022-02-18 (pull request #405)
Updated task scheduler. Approved-by: Patrick Fic
This commit is contained in:
@@ -25204,6 +25204,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>cost_sublet</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>
|
<concept_node>
|
||||||
<name>costs</name>
|
<name>costs</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -27119,6 +27140,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>sale_sublet</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>
|
<concept_node>
|
||||||
<name>sales</name>
|
<name>sales</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -35691,6 +35733,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>exported_payroll</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>
|
</children>
|
||||||
</folder_node>
|
</folder_node>
|
||||||
<folder_node>
|
<folder_node>
|
||||||
|
|||||||
@@ -97,7 +97,9 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
|||||||
render: (text, record) =>
|
render: (text, record) =>
|
||||||
record.cccontracts.length === 1 ? (
|
record.cccontracts.length === 1 ? (
|
||||||
<Link to={`/manage/jobs/${record.cccontracts[0].job.id}`}>
|
<Link to={`/manage/jobs/${record.cccontracts[0].job.id}`}>
|
||||||
{record.cccontracts[0].job.ro_number}
|
{`${record.cccontracts[0].job.ro_number} - ${
|
||||||
|
record.cccontracts[0].job.ownr_fn || ""
|
||||||
|
} ${record.cccontracts[0].job.ownr_ln || ""} ${record.cccontracts[0].job.ownr_co_nm || ""}`}
|
||||||
</Link>
|
</Link>
|
||||||
) : null,
|
) : null,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -23,20 +23,21 @@ export function FormDatePicker({
|
|||||||
onChange,
|
onChange,
|
||||||
onBlur,
|
onBlur,
|
||||||
onlyFuture,
|
onlyFuture,
|
||||||
|
isDateOnly = true,
|
||||||
...restProps
|
...restProps
|
||||||
}) {
|
}) {
|
||||||
const ref = useRef();
|
const ref = useRef();
|
||||||
|
|
||||||
const handleChange = (newDate) => {
|
const handleChange = (newDate) => {
|
||||||
if (value !== newDate && onChange) {
|
if (value !== newDate && onChange) {
|
||||||
onChange(newDate);
|
onChange(isDateOnly ? newDate && newDate.format("YYYY-MM-DD") : newDate);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = (e) => {
|
const handleKeyDown = (e) => {
|
||||||
if (e.key.toLowerCase() === "t") {
|
if (e.key.toLowerCase() === "t") {
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
onChange(moment());
|
onChange(isDateOnly ? moment().format("YYYY-MM-DD") : moment());
|
||||||
// if (ref.current && ref.current.blur) ref.current.blur();
|
// if (ref.current && ref.current.blur) ref.current.blur();
|
||||||
}
|
}
|
||||||
} else if (e.key.toLowerCase() === "enter") {
|
} else if (e.key.toLowerCase() === "enter") {
|
||||||
@@ -64,7 +65,8 @@ export function FormDatePicker({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_a.isValid() && onChange) onChange(_a);
|
if (_a.isValid() && onChange)
|
||||||
|
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -26,19 +26,20 @@ const DateTimePicker = (
|
|||||||
value={value}
|
value={value}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
isDateOnly={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TimePicker
|
<TimePicker
|
||||||
{...restProps}
|
value={value ? moment(value) : null}
|
||||||
value={value ? moment(value) : null}
|
{...(onlyFuture && {
|
||||||
{...(onlyFuture && {
|
disabledDate: (d) => moment().isAfter(d),
|
||||||
disabledDate: (d) => moment().isAfter(d),
|
})}
|
||||||
})}
|
onChange={onChange}
|
||||||
onChange={onChange}
|
showSecond={false}
|
||||||
showSecond={false}
|
minuteStep={15}
|
||||||
minuteStep={15}
|
onBlur={onBlur}
|
||||||
onBlur={onBlur}
|
format="hh:mm a"
|
||||||
format="hh:mm a"
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ export default function JobCostingStatistics({ summaryData }) {
|
|||||||
value={Dinero(summaryData.totalPartsSales).toFormat()}
|
value={Dinero(summaryData.totalPartsSales).toFormat()}
|
||||||
title={t("jobs.labels.sale_parts")}
|
title={t("jobs.labels.sale_parts")}
|
||||||
/>
|
/>
|
||||||
|
<Statistic
|
||||||
|
value={Dinero(summaryData.totalSubletSales).toFormat()}
|
||||||
|
title={t("jobs.labels.sale_sublet")}
|
||||||
|
/>
|
||||||
<Statistic
|
<Statistic
|
||||||
value={Dinero(summaryData.totalAdditionalSales).toFormat()}
|
value={Dinero(summaryData.totalAdditionalSales).toFormat()}
|
||||||
title={t("jobs.labels.sale_additional")}
|
title={t("jobs.labels.sale_additional")}
|
||||||
@@ -32,6 +36,10 @@ export default function JobCostingStatistics({ summaryData }) {
|
|||||||
value={Dinero(summaryData.totalPartsCost).toFormat()}
|
value={Dinero(summaryData.totalPartsCost).toFormat()}
|
||||||
title={t("jobs.labels.cost_parts")}
|
title={t("jobs.labels.cost_parts")}
|
||||||
/>
|
/>
|
||||||
|
<Statistic
|
||||||
|
value={Dinero(summaryData.totalSubletCost).toFormat()}
|
||||||
|
title={t("jobs.labels.cost_sublet")}
|
||||||
|
/>
|
||||||
<Statistic
|
<Statistic
|
||||||
value={Dinero(summaryData.totalAdditionalCost).toFormat()}
|
value={Dinero(summaryData.totalAdditionalCost).toFormat()}
|
||||||
title={t("jobs.labels.cost_Additional")}
|
title={t("jobs.labels.cost_Additional")}
|
||||||
|
|||||||
@@ -33,10 +33,10 @@ export default function JobTotalsTableOther({ job }) {
|
|||||||
key: t("jobs.fields.storage_payable"),
|
key: t("jobs.fields.storage_payable"),
|
||||||
total: job.job_totals.additional.storage,
|
total: job.job_totals.additional.storage,
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
key: t("jobs.fields.ca_bc_pvrt"),
|
// key: t("jobs.fields.ca_bc_pvrt"),
|
||||||
total: job.job_totals.additional.pvrt,
|
// total: job.job_totals.additional.pvrt,
|
||||||
},
|
// },
|
||||||
];
|
];
|
||||||
}, [job.job_totals, t]);
|
}, [job.job_totals, t]);
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,22 @@ import Dinero from "dinero.js";
|
|||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function JobTotalsTableTotals({ job }) {
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
//currentUser: selectCurrentUser
|
||||||
|
bodyshop: selectBodyshop,
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
|
});
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(JobTotalsTableTotals);
|
||||||
|
|
||||||
|
export function JobTotalsTableTotals({ bodyshop, job }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const data = useMemo(() => {
|
const data = useMemo(() => {
|
||||||
@@ -21,6 +36,14 @@ export default function JobTotalsTableTotals({ job }) {
|
|||||||
key: t("jobs.labels.state_tax_amt"),
|
key: t("jobs.labels.state_tax_amt"),
|
||||||
total: job.job_totals.totals.state_tax,
|
total: job.job_totals.totals.state_tax,
|
||||||
},
|
},
|
||||||
|
...(bodyshop.region_config === "CA_BC"
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
key: t("jobs.fields.ca_bc_pvrt"),
|
||||||
|
total: job.job_totals.additional.pvrt,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
{
|
{
|
||||||
key: t("jobs.labels.federal_tax_amt"),
|
key: t("jobs.labels.federal_tax_amt"),
|
||||||
total: job.job_totals.totals.federal_tax,
|
total: job.job_totals.totals.federal_tax,
|
||||||
@@ -58,7 +81,7 @@ export default function JobTotalsTableTotals({ job }) {
|
|||||||
bold: true,
|
bold: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}, [job.job_totals, t]);
|
}, [job.job_totals, t, bodyshop.region_config]);
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
|
|||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "actual_in" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "actual_in" && state.sortedInfo.order,
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<ProductionListDate record={record} field="actual_in" />
|
<ProductionListDate record={record} field="actual_in" time/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -114,6 +114,7 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
|
|||||||
record={record}
|
record={record}
|
||||||
field="scheduled_completion"
|
field="scheduled_completion"
|
||||||
pastIndicator
|
pastIndicator
|
||||||
|
time
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -165,6 +166,7 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
|
|||||||
record={record}
|
record={record}
|
||||||
field="scheduled_delivery"
|
field="scheduled_delivery"
|
||||||
pastIndicator
|
pastIndicator
|
||||||
|
time
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ export default function ProductionListDate({
|
|||||||
|
|
||||||
const handleChange = (date) => {
|
const handleChange = (date) => {
|
||||||
logImEXEvent("product_toggle_date", { field });
|
logImEXEvent("product_toggle_date", { field });
|
||||||
if (date.isSame(record[field] && moment(record[field]))) {
|
// if (date.isSame(record[field] && moment(record[field]))) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
//e.stopPropagation();
|
//e.stopPropagation();
|
||||||
updateAlert({
|
updateAlert({
|
||||||
@@ -67,12 +67,14 @@ export default function ProductionListDate({
|
|||||||
value={(record[field] && moment(record[field])) || null}
|
value={(record[field] && moment(record[field])) || null}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
format="MM/DD/YYYY"
|
format="MM/DD/YYYY"
|
||||||
|
isDateOnly={!time}
|
||||||
/>
|
/>
|
||||||
{time && (
|
{time && (
|
||||||
<TimePicker
|
<TimePicker
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
value={(record[field] && moment(record[field])) || null}
|
value={(record[field] && moment(record[field])) || null}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
minuteStep={15}
|
||||||
format="hh:mm a"
|
format="hh:mm a"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
import { Descriptions, Drawer, Space } from "antd";
|
import { Descriptions, Drawer, Space, PageHeader, Button } from "antd";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -16,8 +16,25 @@ import JobEmployeeAssignments from "../job-employee-assignments/job-employee-ass
|
|||||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||||
import ProductionRemoveButton from "../production-remove-button/production-remove-button.component";
|
import ProductionRemoveButton from "../production-remove-button/production-remove-button.component";
|
||||||
import JobAtChange from "../job-at-change/job-at-change.component";
|
import JobAtChange from "../job-at-change/job-at-change.component";
|
||||||
|
import { PrinterFilled } from "@ant-design/icons";
|
||||||
|
|
||||||
export default function ProductionListDetail({ jobs }) {
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
//currentUser: selectCurrentUser
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
setPrintCenterContext: (context) =>
|
||||||
|
dispatch(setModalContext({ context: context, modal: "printCenter" })),
|
||||||
|
});
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(ProductionListDetail);
|
||||||
|
|
||||||
|
export function ProductionListDetail({ jobs, setPrintCenterContext }) {
|
||||||
const search = queryString.parse(useLocation().search);
|
const search = queryString.parse(useLocation().search);
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { selected } = search;
|
const { selected } = search;
|
||||||
@@ -39,11 +56,29 @@ export default function ProductionListDetail({ jobs }) {
|
|||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
title={
|
title={
|
||||||
<Space>
|
<PageHeader
|
||||||
<span>{t("production.labels.jobdetail")}</span>
|
title={theJob.ro_number}
|
||||||
<span>{theJob.ro_number}</span>
|
extra={
|
||||||
<ProductionRemoveButton jobId={theJob.id} />
|
<Space>
|
||||||
</Space>
|
<ProductionRemoveButton jobId={theJob.id} />{" "}
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setPrintCenterContext({
|
||||||
|
actions: { refetch: refetch },
|
||||||
|
context: {
|
||||||
|
id: theJob.id,
|
||||||
|
job: theJob,
|
||||||
|
type: "job",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PrinterFilled />
|
||||||
|
{t("jobs.actions.printCenter")}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
placement="right"
|
placement="right"
|
||||||
width={"33%"}
|
width={"33%"}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
|
|||||||
<Col {...statSpans}>
|
<Col {...statSpans}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={t("scoreboard.labels.dailyactual")}
|
title={t("scoreboard.labels.dailyactual")}
|
||||||
value={values.todayBody}
|
value={values.todayBody.toFixed(1)}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col {...statSpans}>
|
<Col {...statSpans}>
|
||||||
@@ -116,7 +116,7 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
|
|||||||
<Col {...statSpans}>
|
<Col {...statSpans}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={t("scoreboard.labels.weeklyactual")}
|
title={t("scoreboard.labels.weeklyactual")}
|
||||||
value={values.weeklyBody}
|
value={values.weeklyBody.toFixed(1)}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col {...statSpans}>
|
<Col {...statSpans}>
|
||||||
@@ -140,7 +140,7 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
|
|||||||
<Col {...statSpans}>
|
<Col {...statSpans}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={t("scoreboard.labels.todateactual")}
|
title={t("scoreboard.labels.todateactual")}
|
||||||
value={values.toDateBody}
|
value={values.toDateBody.toFixed(1)}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -152,7 +152,7 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col {...statSpans}>
|
<Col {...statSpans}>
|
||||||
<Statistic value={values.todayPaint} />
|
<Statistic value={values.todayPaint.toFixed(1)} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col {...statSpans}>
|
<Col {...statSpans}>
|
||||||
<Statistic
|
<Statistic
|
||||||
@@ -163,7 +163,7 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col {...statSpans}>
|
<Col {...statSpans}>
|
||||||
<Statistic value={values.weeklyPaint} />
|
<Statistic value={values.weeklyPaint.toFixed(1)} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col {...statSpans}>
|
<Col {...statSpans}>
|
||||||
<Statistic
|
<Statistic
|
||||||
@@ -182,7 +182,7 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col {...statSpans}>
|
<Col {...statSpans}>
|
||||||
<Statistic value={values.toDatePaint} />
|
<Statistic value={values.toDatePaint.toFixed(1)} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -76,6 +76,21 @@ export function TimeTicketList({
|
|||||||
state.sortedInfo.columnKey === "employee" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "employee" && state.sortedInfo.order,
|
||||||
render: (text, record) =>
|
render: (text, record) =>
|
||||||
`${record.employee.first_name} ${record.employee.last_name}`,
|
`${record.employee.first_name} ${record.employee.last_name}`,
|
||||||
|
filters:
|
||||||
|
timetickets
|
||||||
|
.map((l) => l.employeeid)
|
||||||
|
.filter(onlyUnique)
|
||||||
|
.map((s) => {
|
||||||
|
return {
|
||||||
|
text: (() => {
|
||||||
|
const emp = bodyshop.employees.find((e) => e.id === s);
|
||||||
|
|
||||||
|
return `${emp.first_name} ${emp.last_name}`;
|
||||||
|
})(), //
|
||||||
|
value: [s],
|
||||||
|
};
|
||||||
|
}) || [],
|
||||||
|
onFilter: (value, record) => value.includes(record.employeeid),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("timetickets.fields.cost_center"),
|
title: t("timetickets.fields.cost_center"),
|
||||||
|
|||||||
@@ -159,8 +159,10 @@ export function TimeTicketModalComponent({
|
|||||||
name="flat_rate"
|
name="flat_rate"
|
||||||
label={t("timetickets.fields.flat_rate")}
|
label={t("timetickets.fields.flat_rate")}
|
||||||
valuePropName="checked"
|
valuePropName="checked"
|
||||||
|
noStyle
|
||||||
|
style={{ display: "none" }}
|
||||||
>
|
>
|
||||||
<Switch />
|
<Switch style={{ display: "none" }} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
|
|
||||||
@@ -212,6 +214,7 @@ export function TimeTicketModalComponent({
|
|||||||
<>
|
<>
|
||||||
<Form.Item label={t("timetickets.fields.clockon")} name="clockon">
|
<Form.Item label={t("timetickets.fields.clockon")} name="clockon">
|
||||||
<FormDateTimePicker
|
<FormDateTimePicker
|
||||||
|
minuteStep={5}
|
||||||
disabled={
|
disabled={
|
||||||
!HasRbacAccess({
|
!HasRbacAccess({
|
||||||
bodyshop,
|
bodyshop,
|
||||||
@@ -245,6 +248,7 @@ export function TimeTicketModalComponent({
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDateTimePicker
|
<FormDateTimePicker
|
||||||
|
minuteStep={5}
|
||||||
disabled={
|
disabled={
|
||||||
!HasRbacAccess({
|
!HasRbacAccess({
|
||||||
bodyshop,
|
bodyshop,
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { Button } from "antd";
|
||||||
|
import queryString from "query-string";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||||
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import moment from "moment";
|
||||||
|
const PayrollTemplate = TemplateList("special").exported_payroll;
|
||||||
|
export default function TimeTicketsPayrollTable() {
|
||||||
|
const searchParams = queryString.parse(useLocation().search);
|
||||||
|
const { start, end } = searchParams;
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleClick = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
await GenerateDocument(
|
||||||
|
{
|
||||||
|
name: PayrollTemplate.key,
|
||||||
|
variables: {
|
||||||
|
start: start
|
||||||
|
? start
|
||||||
|
: moment().startOf("week").subtract(7, "days").format("YYYY-MM-DD"),
|
||||||
|
end: end ? end : moment().endOf("week").format("YYYY-MM-DD"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
"x"
|
||||||
|
);
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button loading={loading} onClick={handleClick}>
|
||||||
|
{t("printcenter.payments.exported_payroll")}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -89,6 +89,9 @@ export const QUERY_ALL_CC = gql`
|
|||||||
job {
|
job {
|
||||||
id
|
id
|
||||||
ro_number
|
ro_number
|
||||||
|
ownr_fn
|
||||||
|
ownr_ln
|
||||||
|
ownr_co_nm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
import { Col, Row } from "antd";
|
import { Col, Row, Space } from "antd";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
@@ -11,6 +11,7 @@ import AlertComponent from "../../components/alert/alert.component";
|
|||||||
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||||
import TimeTicketsDatesSelector from "../../components/ticket-tickets-dates-selector/time-tickets-dates-selector.component";
|
import TimeTicketsDatesSelector from "../../components/ticket-tickets-dates-selector/time-tickets-dates-selector.component";
|
||||||
import TimeTicketList from "../../components/time-ticket-list/time-ticket-list.component";
|
import TimeTicketList from "../../components/time-ticket-list/time-ticket-list.component";
|
||||||
|
import TimeTicketsPayrollTable from "../../components/time-tickets-payroll-table/time-tickets-payroll-table.component";
|
||||||
import TimeTicketsSummaryEmployees from "../../components/time-tickets-summary-employees/time-tickets-summary-employees.component";
|
import TimeTicketsSummaryEmployees from "../../components/time-tickets-summary-employees/time-tickets-summary-employees.component";
|
||||||
import { QUERY_TIME_TICKETS_IN_RANGE } from "../../graphql/timetickets.queries";
|
import { QUERY_TIME_TICKETS_IN_RANGE } from "../../graphql/timetickets.queries";
|
||||||
import {
|
import {
|
||||||
@@ -68,7 +69,12 @@ export function TimeTicketsContainer({
|
|||||||
<TimeTicketList
|
<TimeTicketList
|
||||||
loading={loading}
|
loading={loading}
|
||||||
timetickets={data ? data.timetickets : []}
|
timetickets={data ? data.timetickets : []}
|
||||||
extra={<TimeTicketsDatesSelector />}
|
extra={
|
||||||
|
<Space wrap>
|
||||||
|
<TimeTicketsPayrollTable />
|
||||||
|
<TimeTicketsDatesSelector />
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"block": "Block Day",
|
"block": "Block Day",
|
||||||
"calculate": "Calculate SMART Dates",
|
"calculate": "Calculate SMART Dates",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel Appointment",
|
||||||
"intake": "Intake",
|
"intake": "Intake",
|
||||||
"new": "New Appointment",
|
"new": "New Appointment",
|
||||||
"preview": "Preview",
|
"preview": "Preview",
|
||||||
@@ -1490,6 +1490,7 @@
|
|||||||
"cost_Additional": "Cost - Additional",
|
"cost_Additional": "Cost - Additional",
|
||||||
"cost_labor": "Cost - Labor",
|
"cost_labor": "Cost - Labor",
|
||||||
"cost_parts": "Cost - Parts",
|
"cost_parts": "Cost - Parts",
|
||||||
|
"cost_sublet": "Cost - Sublet",
|
||||||
"costs": "Costs",
|
"costs": "Costs",
|
||||||
"create": {
|
"create": {
|
||||||
"jobinfo": "Job Info",
|
"jobinfo": "Job Info",
|
||||||
@@ -1589,7 +1590,8 @@
|
|||||||
"rosaletotal": "RO Parts Total",
|
"rosaletotal": "RO Parts Total",
|
||||||
"sale_additional": "Sales - Additional",
|
"sale_additional": "Sales - Additional",
|
||||||
"sale_labor": "Sales - Labor",
|
"sale_labor": "Sales - Labor",
|
||||||
"sale_parts": "Sales - Parts & Sublet",
|
"sale_parts": "Sales - Parts",
|
||||||
|
"sale_sublet": "Sales - Sublet",
|
||||||
"sales": "Sales",
|
"sales": "Sales",
|
||||||
"savebeforeconversion": "You have unsaved changes on the job. Please save them before converting it. ",
|
"savebeforeconversion": "You have unsaved changes on the job. Please save them before converting it. ",
|
||||||
"scheduledinchange": "The scheduled in is based off the latest appointment. To change this date, please schedule or reschedule the job. ",
|
"scheduledinchange": "The scheduled in is based off the latest appointment. To change this date, please schedule or reschedule the job. ",
|
||||||
@@ -2117,7 +2119,8 @@
|
|||||||
"title": "Print Center"
|
"title": "Print Center"
|
||||||
},
|
},
|
||||||
"payments": {
|
"payments": {
|
||||||
"ca_bc_etf_table": "ICBC ETF Table"
|
"ca_bc_etf_table": "ICBC ETF Table",
|
||||||
|
"exported_payroll": "Payroll Table"
|
||||||
},
|
},
|
||||||
"subjects": {
|
"subjects": {
|
||||||
"jobs": {
|
"jobs": {
|
||||||
|
|||||||
@@ -1490,6 +1490,7 @@
|
|||||||
"cost_Additional": "",
|
"cost_Additional": "",
|
||||||
"cost_labor": "",
|
"cost_labor": "",
|
||||||
"cost_parts": "",
|
"cost_parts": "",
|
||||||
|
"cost_sublet": "",
|
||||||
"costs": "",
|
"costs": "",
|
||||||
"create": {
|
"create": {
|
||||||
"jobinfo": "",
|
"jobinfo": "",
|
||||||
@@ -1590,6 +1591,7 @@
|
|||||||
"sale_additional": "",
|
"sale_additional": "",
|
||||||
"sale_labor": "",
|
"sale_labor": "",
|
||||||
"sale_parts": "",
|
"sale_parts": "",
|
||||||
|
"sale_sublet": "",
|
||||||
"sales": "",
|
"sales": "",
|
||||||
"savebeforeconversion": "",
|
"savebeforeconversion": "",
|
||||||
"scheduledinchange": "",
|
"scheduledinchange": "",
|
||||||
@@ -2117,7 +2119,8 @@
|
|||||||
"title": ""
|
"title": ""
|
||||||
},
|
},
|
||||||
"payments": {
|
"payments": {
|
||||||
"ca_bc_etf_table": ""
|
"ca_bc_etf_table": "",
|
||||||
|
"exported_payroll": ""
|
||||||
},
|
},
|
||||||
"subjects": {
|
"subjects": {
|
||||||
"jobs": {
|
"jobs": {
|
||||||
|
|||||||
@@ -1490,6 +1490,7 @@
|
|||||||
"cost_Additional": "",
|
"cost_Additional": "",
|
||||||
"cost_labor": "",
|
"cost_labor": "",
|
||||||
"cost_parts": "",
|
"cost_parts": "",
|
||||||
|
"cost_sublet": "",
|
||||||
"costs": "",
|
"costs": "",
|
||||||
"create": {
|
"create": {
|
||||||
"jobinfo": "",
|
"jobinfo": "",
|
||||||
@@ -1590,6 +1591,7 @@
|
|||||||
"sale_additional": "",
|
"sale_additional": "",
|
||||||
"sale_labor": "",
|
"sale_labor": "",
|
||||||
"sale_parts": "",
|
"sale_parts": "",
|
||||||
|
"sale_sublet": "",
|
||||||
"sales": "",
|
"sales": "",
|
||||||
"savebeforeconversion": "",
|
"savebeforeconversion": "",
|
||||||
"scheduledinchange": "",
|
"scheduledinchange": "",
|
||||||
@@ -2117,7 +2119,8 @@
|
|||||||
"title": ""
|
"title": ""
|
||||||
},
|
},
|
||||||
"payments": {
|
"payments": {
|
||||||
"ca_bc_etf_table": ""
|
"ca_bc_etf_table": "",
|
||||||
|
"exported_payroll": ""
|
||||||
},
|
},
|
||||||
"subjects": {
|
"subjects": {
|
||||||
"jobs": {
|
"jobs": {
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ const Templates = TemplateList();
|
|||||||
export default async function RenderTemplate(
|
export default async function RenderTemplate(
|
||||||
templateObject,
|
templateObject,
|
||||||
bodyshop,
|
bodyshop,
|
||||||
renderAsHtml = false
|
renderAsHtml = false,
|
||||||
|
renderAsExcel = false
|
||||||
) {
|
) {
|
||||||
//Query assets that match the template name. Must be in format <<templateName>>.query
|
//Query assets that match the template name. Must be in format <<templateName>>.query
|
||||||
let { contextData, useShopSpecificTemplate } = await fetchContextData(
|
let { contextData, useShopSpecificTemplate } = await fetchContextData(
|
||||||
@@ -30,6 +31,7 @@ export default async function RenderTemplate(
|
|||||||
? `/${bodyshop.imexshopid}/${templateObject.name}`
|
? `/${bodyshop.imexshopid}/${templateObject.name}`
|
||||||
: `/${templateObject.name}`,
|
: `/${templateObject.name}`,
|
||||||
...(renderAsHtml ? {} : { recipe: "chrome-pdf" }),
|
...(renderAsHtml ? {} : { recipe: "chrome-pdf" }),
|
||||||
|
...(renderAsExcel ? { recipe: "html-to-xlsx" } : {}),
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
...contextData,
|
...contextData,
|
||||||
@@ -182,6 +184,9 @@ export const GenerateDocument = async (
|
|||||||
template,
|
template,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
} else if (sendType === "x") {
|
||||||
|
console.log("excel");
|
||||||
|
await RenderTemplate(template, bodyshop, false, true);
|
||||||
} else {
|
} else {
|
||||||
await RenderTemplate(template, bodyshop);
|
await RenderTemplate(template, bodyshop);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1635,6 +1635,13 @@ export const TemplateList = (type, context) => {
|
|||||||
key: "ca_bc_etf_table",
|
key: "ca_bc_etf_table",
|
||||||
disabled: false,
|
disabled: false,
|
||||||
},
|
},
|
||||||
|
exported_payroll: {
|
||||||
|
title: i18n.t("printcenter.payments.exported_payroll"),
|
||||||
|
description: "Est Detail",
|
||||||
|
subject: i18n.t("printcenter.payments.exported_payroll"),
|
||||||
|
key: "exported_payroll",
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
production_by_technician_one: {
|
production_by_technician_one: {
|
||||||
title: i18n.t(
|
title: i18n.t(
|
||||||
"reportcenter.templates.production_by_technician_one"
|
"reportcenter.templates.production_by_technician_one"
|
||||||
|
|||||||
@@ -63,10 +63,12 @@ async function JobCostingMulti(req, res) {
|
|||||||
totalLaborSales: Dinero({ amount: 0 }),
|
totalLaborSales: Dinero({ amount: 0 }),
|
||||||
totalPartsSales: Dinero({ amount: 0 }),
|
totalPartsSales: Dinero({ amount: 0 }),
|
||||||
totalAdditionalSales: Dinero({ amount: 0 }),
|
totalAdditionalSales: Dinero({ amount: 0 }),
|
||||||
|
totalSubletSales: Dinero({ amount: 0 }),
|
||||||
totalSales: Dinero({ amount: 0 }),
|
totalSales: Dinero({ amount: 0 }),
|
||||||
totalLaborCost: Dinero({ amount: 0 }),
|
totalLaborCost: Dinero({ amount: 0 }),
|
||||||
totalPartsCost: Dinero({ amount: 0 }),
|
totalPartsCost: Dinero({ amount: 0 }),
|
||||||
totalAdditionalCost: Dinero({ amount: 0 }),
|
totalAdditionalCost: Dinero({ amount: 0 }),
|
||||||
|
totalSubletCost: Dinero({ amount: 0 }),
|
||||||
totalCost: Dinero({ amount: 0 }),
|
totalCost: Dinero({ amount: 0 }),
|
||||||
gpdollars: Dinero({ amount: 0 }),
|
gpdollars: Dinero({ amount: 0 }),
|
||||||
gppercent: null,
|
gppercent: null,
|
||||||
@@ -74,12 +76,15 @@ async function JobCostingMulti(req, res) {
|
|||||||
totalLaborGp: Dinero({ amount: 0 }),
|
totalLaborGp: Dinero({ amount: 0 }),
|
||||||
totalPartsGp: Dinero({ amount: 0 }),
|
totalPartsGp: Dinero({ amount: 0 }),
|
||||||
totalAdditionalGp: Dinero({ amount: 0 }),
|
totalAdditionalGp: Dinero({ amount: 0 }),
|
||||||
|
totalSubletGp: Dinero({ amount: 0 }),
|
||||||
totalLaborGppercent: null,
|
totalLaborGppercent: null,
|
||||||
totalLaborGppercentFormatted: null,
|
totalLaborGppercentFormatted: null,
|
||||||
totalPartsGppercent: null,
|
totalPartsGppercent: null,
|
||||||
totalPartsGppercentFormatted: null,
|
totalPartsGppercentFormatted: null,
|
||||||
totalAdditionalGppercent: null,
|
totalAdditionalGppercent: null,
|
||||||
totalAdditionalGppercentFormatted: null,
|
totalAdditionalGppercentFormatted: null,
|
||||||
|
totalSubletGppercent: null,
|
||||||
|
totalSubletGppercentFormatted: null,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -110,6 +115,9 @@ async function JobCostingMulti(req, res) {
|
|||||||
sale_additional_dinero: multiSummary.costCenterData[
|
sale_additional_dinero: multiSummary.costCenterData[
|
||||||
CostCenterIndex
|
CostCenterIndex
|
||||||
].sale_additional_dinero.add(c.sale_additional_dinero),
|
].sale_additional_dinero.add(c.sale_additional_dinero),
|
||||||
|
sale_sublet_dinero: multiSummary.costCenterData[
|
||||||
|
CostCenterIndex
|
||||||
|
].sale_sublet_dinero.add(c.sale_sublet_dinero),
|
||||||
cost_labor_dinero: multiSummary.costCenterData[
|
cost_labor_dinero: multiSummary.costCenterData[
|
||||||
CostCenterIndex
|
CostCenterIndex
|
||||||
].cost_labor_dinero.add(c.cost_labor_dinero),
|
].cost_labor_dinero.add(c.cost_labor_dinero),
|
||||||
@@ -119,6 +127,9 @@ async function JobCostingMulti(req, res) {
|
|||||||
cost_additional_dinero: multiSummary.costCenterData[
|
cost_additional_dinero: multiSummary.costCenterData[
|
||||||
CostCenterIndex
|
CostCenterIndex
|
||||||
].cost_additional_dinero.add(c.cost_additional_dinero),
|
].cost_additional_dinero.add(c.cost_additional_dinero),
|
||||||
|
cost_sublet_dinero: multiSummary.costCenterData[
|
||||||
|
CostCenterIndex
|
||||||
|
].cost_sublet_dinero.add(c.cost_sublet_dinero),
|
||||||
gpdollars_dinero: multiSummary.costCenterData[
|
gpdollars_dinero: multiSummary.costCenterData[
|
||||||
CostCenterIndex
|
CostCenterIndex
|
||||||
].gpdollars_dinero.add(c.gpdollars_dinero),
|
].gpdollars_dinero.add(c.gpdollars_dinero),
|
||||||
@@ -144,6 +155,10 @@ async function JobCostingMulti(req, res) {
|
|||||||
multiSummary.summaryData.totalAdditionalSales.add(
|
multiSummary.summaryData.totalAdditionalSales.add(
|
||||||
costingData.summaryData.totalAdditionalSales
|
costingData.summaryData.totalAdditionalSales
|
||||||
);
|
);
|
||||||
|
multiSummary.summaryData.totalSubletSales =
|
||||||
|
multiSummary.summaryData.totalSubletSales.add(
|
||||||
|
costingData.summaryData.totalSubletSales
|
||||||
|
);
|
||||||
multiSummary.summaryData.totalSales =
|
multiSummary.summaryData.totalSales =
|
||||||
multiSummary.summaryData.totalSales.add(
|
multiSummary.summaryData.totalSales.add(
|
||||||
costingData.summaryData.totalSales
|
costingData.summaryData.totalSales
|
||||||
@@ -164,6 +179,10 @@ async function JobCostingMulti(req, res) {
|
|||||||
multiSummary.summaryData.totalAdditionalCost.add(
|
multiSummary.summaryData.totalAdditionalCost.add(
|
||||||
costingData.summaryData.totalAdditionalCost
|
costingData.summaryData.totalAdditionalCost
|
||||||
);
|
);
|
||||||
|
multiSummary.summaryData.totalSubletCost =
|
||||||
|
multiSummary.summaryData.totalSubletCost.add(
|
||||||
|
costingData.summaryData.totalSubletCost
|
||||||
|
);
|
||||||
multiSummary.summaryData.totalCost =
|
multiSummary.summaryData.totalCost =
|
||||||
multiSummary.summaryData.totalCost.add(
|
multiSummary.summaryData.totalCost.add(
|
||||||
costingData.summaryData.totalCost
|
costingData.summaryData.totalCost
|
||||||
@@ -185,6 +204,10 @@ async function JobCostingMulti(req, res) {
|
|||||||
multiSummary.summaryData.totalAdditionalGp.add(
|
multiSummary.summaryData.totalAdditionalGp.add(
|
||||||
costingData.summaryData.totalAdditionalGp
|
costingData.summaryData.totalAdditionalGp
|
||||||
);
|
);
|
||||||
|
multiSummary.summaryData.totalSubletGp =
|
||||||
|
multiSummary.summaryData.totalSubletGp.add(
|
||||||
|
costingData.summaryData.totalSubletGp
|
||||||
|
);
|
||||||
|
|
||||||
//Take the summary data & add it to total summary data.
|
//Take the summary data & add it to total summary data.
|
||||||
});
|
});
|
||||||
@@ -219,6 +242,16 @@ async function JobCostingMulti(req, res) {
|
|||||||
multiSummary.summaryData.totalAdditionalGppercentFormatted =
|
multiSummary.summaryData.totalAdditionalGppercentFormatted =
|
||||||
formatGpPercent(multiSummary.summaryData.totalAdditionalGppercent);
|
formatGpPercent(multiSummary.summaryData.totalAdditionalGppercent);
|
||||||
|
|
||||||
|
multiSummary.summaryData.totalSubletGppercent = (
|
||||||
|
(multiSummary.summaryData.totalSubletGp.getAmount() /
|
||||||
|
multiSummary.summaryData.totalSubletSales.getAmount()) *
|
||||||
|
100
|
||||||
|
).toFixed(2);
|
||||||
|
|
||||||
|
multiSummary.summaryData.totalSubletGppercentFormatted = formatGpPercent(
|
||||||
|
multiSummary.summaryData.totalSubletGppercent
|
||||||
|
);
|
||||||
|
|
||||||
multiSummary.summaryData.gppercent = (
|
multiSummary.summaryData.gppercent = (
|
||||||
(multiSummary.summaryData.gpdollars.getAmount() /
|
(multiSummary.summaryData.gpdollars.getAmount() /
|
||||||
multiSummary.summaryData.totalSales.getAmount()) *
|
multiSummary.summaryData.totalSales.getAmount()) *
|
||||||
@@ -236,11 +269,13 @@ async function JobCostingMulti(req, res) {
|
|||||||
sale_parts: c.sale_parts_dinero && c.sale_parts_dinero.toFormat(),
|
sale_parts: c.sale_parts_dinero && c.sale_parts_dinero.toFormat(),
|
||||||
sale_additional:
|
sale_additional:
|
||||||
c.sale_additional_dinero && c.sale_additional_dinero.toFormat(),
|
c.sale_additional_dinero && c.sale_additional_dinero.toFormat(),
|
||||||
|
sale_sublet: c.sale_sublet_dinero && c.sale_sublet_dinero.toFormat(),
|
||||||
sales: c.sales_dinero.toFormat(),
|
sales: c.sales_dinero.toFormat(),
|
||||||
cost_parts: c.cost_parts_dinero && c.cost_parts_dinero.toFormat(),
|
cost_parts: c.cost_parts_dinero && c.cost_parts_dinero.toFormat(),
|
||||||
cost_labor: c.cost_labor_dinero && c.cost_labor_dinero.toFormat(),
|
cost_labor: c.cost_labor_dinero && c.cost_labor_dinero.toFormat(),
|
||||||
cost_additional:
|
cost_additional:
|
||||||
c.cost_additional_dinero && c.cost_additional_dinero.toFormat(),
|
c.cost_additional_dinero && c.cost_additional_dinero.toFormat(),
|
||||||
|
cost_sublet: c.cost_sublet_dinero && c.cost_sublet_dinero.toFormat(),
|
||||||
costs: c.costs_dinero.toFormat(),
|
costs: c.costs_dinero.toFormat(),
|
||||||
gpdollars: c.gpdollars_dinero.toFormat(),
|
gpdollars: c.gpdollars_dinero.toFormat(),
|
||||||
gppercent: formatGpPercent(
|
gppercent: formatGpPercent(
|
||||||
@@ -269,9 +304,6 @@ async function JobCostingMulti(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function GenerateCostingData(job) {
|
function GenerateCostingData(job) {
|
||||||
if (job.id === "b97353ef-24c8-4b3f-a6c1-2190391c823e") {
|
|
||||||
console.log("here");
|
|
||||||
}
|
|
||||||
const defaultProfits =
|
const defaultProfits =
|
||||||
job.bodyshop.md_responsibility_centers.defaults.profits;
|
job.bodyshop.md_responsibility_centers.defaults.profits;
|
||||||
const allCenters = _.union(
|
const allCenters = _.union(
|
||||||
@@ -320,7 +352,12 @@ function GenerateCostingData(job) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (val.part_type && val.part_type !== "PAE") {
|
if (
|
||||||
|
val.part_type &&
|
||||||
|
val.part_type !== "PAE" &&
|
||||||
|
val.part_type !== "PAS" &&
|
||||||
|
val.part_type !== "PASL"
|
||||||
|
) {
|
||||||
const partsProfitCenter =
|
const partsProfitCenter =
|
||||||
val.profitcenter_part || defaultProfits[val.part_type] || "?";
|
val.profitcenter_part || defaultProfits[val.part_type] || "?";
|
||||||
|
|
||||||
@@ -352,6 +389,42 @@ function GenerateCostingData(job) {
|
|||||||
acc.parts[partsProfitCenter] =
|
acc.parts[partsProfitCenter] =
|
||||||
acc.parts[partsProfitCenter].add(partsAmount);
|
acc.parts[partsProfitCenter].add(partsAmount);
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
val.part_type &&
|
||||||
|
val.part_type !== "PAE" &&
|
||||||
|
(val.part_type === "PAS" || val.part_type === "PASL")
|
||||||
|
) {
|
||||||
|
const partsProfitCenter =
|
||||||
|
val.profitcenter_part || defaultProfits[val.part_type] || "?";
|
||||||
|
|
||||||
|
if (partsProfitCenter === "?")
|
||||||
|
console.log("Unknown type", val.line_desc, val.part_type);
|
||||||
|
|
||||||
|
if (!partsProfitCenter)
|
||||||
|
console.log(
|
||||||
|
"Unknown cost/profit center mapping for sublet.",
|
||||||
|
val.line_desc,
|
||||||
|
val.part_type
|
||||||
|
);
|
||||||
|
const partsAmount = Dinero({
|
||||||
|
amount: Math.round((val.act_price || 0) * 100),
|
||||||
|
})
|
||||||
|
.multiply(val.part_qty || 1)
|
||||||
|
.add(
|
||||||
|
val.prt_dsmk_m && val.prt_dsmk_m !== 0
|
||||||
|
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
|
||||||
|
: Dinero({
|
||||||
|
amount: Math.round(val.act_price * 100),
|
||||||
|
})
|
||||||
|
.multiply(val.part_qty || 0)
|
||||||
|
.percentage(Math.abs(val.prt_dsmk_p || 0))
|
||||||
|
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
|
||||||
|
);
|
||||||
|
if (!acc.sublet[partsProfitCenter])
|
||||||
|
acc.sublet[partsProfitCenter] = Dinero();
|
||||||
|
acc.sublet[partsProfitCenter] =
|
||||||
|
acc.sublet[partsProfitCenter].add(partsAmount);
|
||||||
|
}
|
||||||
|
|
||||||
//To deal with additional costs.
|
//To deal with additional costs.
|
||||||
if (!val.part_type && !val.mod_lbr_ty) {
|
if (!val.part_type && !val.mod_lbr_ty) {
|
||||||
@@ -389,7 +462,7 @@ function GenerateCostingData(job) {
|
|||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
{ parts: {}, labor: {}, additional: {} }
|
{ parts: {}, labor: {}, additional: {}, sublet: {} }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!hasMapaLine) {
|
if (!hasMapaLine) {
|
||||||
@@ -444,6 +517,12 @@ function GenerateCostingData(job) {
|
|||||||
.multiply(bill_val.is_credit_memo ? -1 : 1)
|
.multiply(bill_val.is_credit_memo ? -1 : 1)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
const isSubletCostCenter =
|
||||||
|
line_val.cost_center ===
|
||||||
|
job.bodyshop.md_responsibility_centers.defaults.costs.PAS ||
|
||||||
|
line_val.cost_center ===
|
||||||
|
job.bodyshop.md_responsibility_centers.defaults.costs.PASL;
|
||||||
|
|
||||||
const isAdditionalCostCenter =
|
const isAdditionalCostCenter =
|
||||||
// line_val.cost_center ===
|
// line_val.cost_center ===
|
||||||
// job.bodyshop.md_responsibility_centers.defaults.costs.PAS ||
|
// job.bodyshop.md_responsibility_centers.defaults.costs.PAS ||
|
||||||
@@ -468,6 +547,19 @@ function GenerateCostingData(job) {
|
|||||||
.multiply(line_val.quantity)
|
.multiply(line_val.quantity)
|
||||||
.multiply(bill_val.is_credit_memo ? -1 : 1)
|
.multiply(bill_val.is_credit_memo ? -1 : 1)
|
||||||
);
|
);
|
||||||
|
} else if (isSubletCostCenter) {
|
||||||
|
if (!bill_acc.subletCosts[line_val.cost_center])
|
||||||
|
bill_acc.subletCosts[line_val.cost_center] = Dinero();
|
||||||
|
|
||||||
|
bill_acc.subletCosts[line_val.cost_center] = bill_acc.subletCosts[
|
||||||
|
line_val.cost_center
|
||||||
|
].add(
|
||||||
|
Dinero({
|
||||||
|
amount: Math.round((line_val.actual_cost || 0) * 100),
|
||||||
|
})
|
||||||
|
.multiply(line_val.quantity)
|
||||||
|
.multiply(bill_val.is_credit_memo ? -1 : 1)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
if (!bill_acc[line_val.cost_center])
|
if (!bill_acc[line_val.cost_center])
|
||||||
bill_acc[line_val.cost_center] = Dinero();
|
bill_acc[line_val.cost_center] = Dinero();
|
||||||
@@ -486,7 +578,7 @@ function GenerateCostingData(job) {
|
|||||||
});
|
});
|
||||||
return bill_acc;
|
return bill_acc;
|
||||||
},
|
},
|
||||||
{ additionalCosts: {} }
|
{ additionalCosts: {}, subletCosts: {} }
|
||||||
);
|
);
|
||||||
|
|
||||||
//If the hourly rates for job costing are set, add them in.
|
//If the hourly rates for job costing are set, add them in.
|
||||||
@@ -586,14 +678,17 @@ function GenerateCostingData(job) {
|
|||||||
totalLaborSales: Dinero({ amount: 0 }),
|
totalLaborSales: Dinero({ amount: 0 }),
|
||||||
totalPartsSales: Dinero({ amount: 0 }),
|
totalPartsSales: Dinero({ amount: 0 }),
|
||||||
totalAdditionalSales: Dinero({ amount: 0 }),
|
totalAdditionalSales: Dinero({ amount: 0 }),
|
||||||
|
totalSubletSales: Dinero({ amount: 0 }),
|
||||||
totalSales: Dinero({ amount: 0 }),
|
totalSales: Dinero({ amount: 0 }),
|
||||||
totalLaborCost: Dinero({ amount: 0 }),
|
totalLaborCost: Dinero({ amount: 0 }),
|
||||||
totalPartsCost: Dinero({ amount: 0 }),
|
totalPartsCost: Dinero({ amount: 0 }),
|
||||||
totalAdditionalCost: Dinero({ amount: 0 }),
|
totalAdditionalCost: Dinero({ amount: 0 }),
|
||||||
|
totalSubletCost: Dinero({ amount: 0 }),
|
||||||
totalCost: Dinero({ amount: 0 }),
|
totalCost: Dinero({ amount: 0 }),
|
||||||
totalLaborGp: Dinero({ amount: 0 }),
|
totalLaborGp: Dinero({ amount: 0 }),
|
||||||
totalPartsGp: Dinero({ amount: 0 }),
|
totalPartsGp: Dinero({ amount: 0 }),
|
||||||
totalAdditionalGp: Dinero({ amount: 0 }),
|
totalAdditionalGp: Dinero({ amount: 0 }),
|
||||||
|
totalSubletGp: Dinero({ amount: 0 }),
|
||||||
gpdollars: Dinero({ amount: 0 }),
|
gpdollars: Dinero({ amount: 0 }),
|
||||||
totalLaborGppercent: null,
|
totalLaborGppercent: null,
|
||||||
totalLaborGppercentFormatted: null,
|
totalLaborGppercentFormatted: null,
|
||||||
@@ -601,6 +696,8 @@ function GenerateCostingData(job) {
|
|||||||
totalPartsGppercentFormatted: null,
|
totalPartsGppercentFormatted: null,
|
||||||
totalAdditionalGppercent: null,
|
totalAdditionalGppercent: null,
|
||||||
totalAdditionalGppercentFormatted: null,
|
totalAdditionalGppercentFormatted: null,
|
||||||
|
totalSubletGppercent: null,
|
||||||
|
totalSubletGppercentFormatted: null,
|
||||||
gppercent: null,
|
gppercent: null,
|
||||||
gppercentFormatted: null,
|
gppercentFormatted: null,
|
||||||
};
|
};
|
||||||
@@ -613,14 +710,24 @@ function GenerateCostingData(job) {
|
|||||||
jobLineTotalsByProfitCenter.parts[ccVal] || Dinero({ amount: 0 });
|
jobLineTotalsByProfitCenter.parts[ccVal] || Dinero({ amount: 0 });
|
||||||
const sale_additional =
|
const sale_additional =
|
||||||
jobLineTotalsByProfitCenter.additional[ccVal] || Dinero({ amount: 0 });
|
jobLineTotalsByProfitCenter.additional[ccVal] || Dinero({ amount: 0 });
|
||||||
|
const sale_sublet =
|
||||||
|
jobLineTotalsByProfitCenter.sublet[ccVal] || Dinero({ amount: 0 });
|
||||||
|
|
||||||
const cost_labor = ticketTotalsByCostCenter[ccVal] || Dinero({ amount: 0 });
|
const cost_labor = ticketTotalsByCostCenter[ccVal] || Dinero({ amount: 0 });
|
||||||
const cost_parts = billTotalsByCostCenters[ccVal] || Dinero({ amount: 0 });
|
const cost_parts = billTotalsByCostCenters[ccVal] || Dinero({ amount: 0 });
|
||||||
const cost_additional =
|
const cost_additional =
|
||||||
billTotalsByCostCenters.additionalCosts[ccVal] || Dinero({ amount: 0 });
|
billTotalsByCostCenters.additionalCosts[ccVal] || Dinero({ amount: 0 });
|
||||||
|
const cost_sublet =
|
||||||
|
billTotalsByCostCenters.subletCosts[ccVal] || Dinero({ amount: 0 });
|
||||||
|
|
||||||
const costs = cost_labor.add(cost_parts).add(cost_additional);
|
const costs = cost_labor
|
||||||
const totalSales = sale_labor.add(sale_parts).add(sale_additional);
|
.add(cost_parts)
|
||||||
|
.add(cost_additional)
|
||||||
|
.add(cost_sublet);
|
||||||
|
const totalSales = sale_labor
|
||||||
|
.add(sale_parts)
|
||||||
|
.add(sale_additional)
|
||||||
|
.add(sale_sublet);
|
||||||
const gpdollars = totalSales.subtract(costs);
|
const gpdollars = totalSales.subtract(costs);
|
||||||
const gppercent = (
|
const gppercent = (
|
||||||
(gpdollars.getAmount() / totalSales.getAmount()) *
|
(gpdollars.getAmount() / totalSales.getAmount()) *
|
||||||
@@ -632,11 +739,14 @@ function GenerateCostingData(job) {
|
|||||||
summaryData.totalPartsSales = summaryData.totalPartsSales.add(sale_parts);
|
summaryData.totalPartsSales = summaryData.totalPartsSales.add(sale_parts);
|
||||||
summaryData.totalAdditionalSales =
|
summaryData.totalAdditionalSales =
|
||||||
summaryData.totalAdditionalSales.add(sale_additional);
|
summaryData.totalAdditionalSales.add(sale_additional);
|
||||||
|
summaryData.totalSubletSales =
|
||||||
|
summaryData.totalSubletSales.add(sale_sublet);
|
||||||
summaryData.totalSales = summaryData.totalSales.add(totalSales);
|
summaryData.totalSales = summaryData.totalSales.add(totalSales);
|
||||||
summaryData.totalLaborCost = summaryData.totalLaborCost.add(cost_labor);
|
summaryData.totalLaborCost = summaryData.totalLaborCost.add(cost_labor);
|
||||||
summaryData.totalPartsCost = summaryData.totalPartsCost.add(cost_parts);
|
summaryData.totalPartsCost = summaryData.totalPartsCost.add(cost_parts);
|
||||||
summaryData.totalAdditionalCost =
|
summaryData.totalAdditionalCost =
|
||||||
summaryData.totalAdditionalCost.add(cost_additional);
|
summaryData.totalAdditionalCost.add(cost_additional);
|
||||||
|
summaryData.totalSubletCost = summaryData.totalSubletCost.add(cost_sublet);
|
||||||
summaryData.totalCost = summaryData.totalCost.add(costs);
|
summaryData.totalCost = summaryData.totalCost.add(costs);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -648,6 +758,8 @@ function GenerateCostingData(job) {
|
|||||||
sale_parts_dinero: sale_parts,
|
sale_parts_dinero: sale_parts,
|
||||||
sale_additional: sale_additional && sale_additional.toFormat(),
|
sale_additional: sale_additional && sale_additional.toFormat(),
|
||||||
sale_additional_dinero: sale_additional,
|
sale_additional_dinero: sale_additional,
|
||||||
|
sale_sublet: sale_sublet && sale_sublet.toFormat(),
|
||||||
|
sale_sublet_dinero: sale_sublet,
|
||||||
sales: totalSales.toFormat(),
|
sales: totalSales.toFormat(),
|
||||||
sales_dinero: totalSales,
|
sales_dinero: totalSales,
|
||||||
cost_parts: cost_parts && cost_parts.toFormat(),
|
cost_parts: cost_parts && cost_parts.toFormat(),
|
||||||
@@ -656,6 +768,8 @@ function GenerateCostingData(job) {
|
|||||||
cost_labor_dinero: cost_labor,
|
cost_labor_dinero: cost_labor,
|
||||||
cost_additional: cost_additional && cost_additional.toFormat(),
|
cost_additional: cost_additional && cost_additional.toFormat(),
|
||||||
cost_additional_dinero: cost_additional,
|
cost_additional_dinero: cost_additional,
|
||||||
|
cost_sublet: cost_sublet && cost_sublet.toFormat(),
|
||||||
|
cost_sublet_dinero: cost_sublet,
|
||||||
costs: costs.toFormat(),
|
costs: costs.toFormat(),
|
||||||
costs_dinero: costs,
|
costs_dinero: costs,
|
||||||
gpdollars_dinero: gpdollars,
|
gpdollars_dinero: gpdollars,
|
||||||
@@ -678,8 +792,10 @@ function GenerateCostingData(job) {
|
|||||||
sale_labor_dinero: Dinero(),
|
sale_labor_dinero: Dinero(),
|
||||||
sale_parts: Dinero().toFormat(),
|
sale_parts: Dinero().toFormat(),
|
||||||
sale_parts_dinero: Dinero(),
|
sale_parts_dinero: Dinero(),
|
||||||
sale_additional: Dinero(),
|
sale_additional: Dinero(),
|
||||||
sale_additional_dinero: Dinero(),
|
sale_additional_dinero: Dinero(),
|
||||||
|
sale_sublet: Dinero(),
|
||||||
|
sale_sublet_dinero: Dinero(),
|
||||||
sales: Dinero().toFormat(),
|
sales: Dinero().toFormat(),
|
||||||
sales_dinero: Dinero(),
|
sales_dinero: Dinero(),
|
||||||
cost_parts: Dinero().toFormat(),
|
cost_parts: Dinero().toFormat(),
|
||||||
@@ -688,6 +804,8 @@ function GenerateCostingData(job) {
|
|||||||
cost_labor_dinero: Adjustment,
|
cost_labor_dinero: Adjustment,
|
||||||
cost_additional: Dinero(),
|
cost_additional: Dinero(),
|
||||||
cost_additional_dinero: Dinero(),
|
cost_additional_dinero: Dinero(),
|
||||||
|
cost_sublet: Dinero(),
|
||||||
|
cost_sublet_dinero: Dinero(),
|
||||||
costs: Adjustment.toFormat(),
|
costs: Adjustment.toFormat(),
|
||||||
costs_dinero: Adjustment,
|
costs_dinero: Adjustment,
|
||||||
gpdollars_dinero: Dinero(),
|
gpdollars_dinero: Dinero(),
|
||||||
@@ -732,6 +850,17 @@ function GenerateCostingData(job) {
|
|||||||
summaryData.totalAdditionalGppercentFormatted = formatGpPercent(
|
summaryData.totalAdditionalGppercentFormatted = formatGpPercent(
|
||||||
summaryData.totalAdditionalGppercent
|
summaryData.totalAdditionalGppercent
|
||||||
);
|
);
|
||||||
|
summaryData.totalSubletGp = summaryData.totalSubletSales.subtract(
|
||||||
|
summaryData.totalSubletCost
|
||||||
|
);
|
||||||
|
summaryData.totalSubletGppercent = (
|
||||||
|
(summaryData.totalSubletGp.getAmount() /
|
||||||
|
summaryData.totalSubletSales.getAmount()) *
|
||||||
|
100
|
||||||
|
).toFixed(2);
|
||||||
|
summaryData.totalSubletGppercentFormatted = formatGpPercent(
|
||||||
|
summaryData.totalSubletGppercent
|
||||||
|
);
|
||||||
|
|
||||||
summaryData.gpdollars = summaryData.totalSales.subtract(
|
summaryData.gpdollars = summaryData.totalSales.subtract(
|
||||||
summaryData.totalCost
|
summaryData.totalCost
|
||||||
|
|||||||
@@ -442,8 +442,8 @@ function CalculateAdditional(job) {
|
|||||||
ret.total = ret.additionalCosts
|
ret.total = ret.additionalCosts
|
||||||
.add(ret.adjustments) //IO-813 Adjustment takes care of GST & PST at labor rate.
|
.add(ret.adjustments) //IO-813 Adjustment takes care of GST & PST at labor rate.
|
||||||
.add(ret.towing)
|
.add(ret.towing)
|
||||||
.add(ret.storage)
|
.add(ret.storage);
|
||||||
.add(ret.pvrt);
|
//.add(ret.pvrt);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -453,6 +453,7 @@ function CalculateTaxesTotals(job, otherTotals) {
|
|||||||
.add(otherTotals.parts.sublets.subtotal)
|
.add(otherTotals.parts.sublets.subtotal)
|
||||||
.add(otherTotals.rates.subtotal) //No longer using just rates subtotal to include mapa/mash.
|
.add(otherTotals.rates.subtotal) //No longer using just rates subtotal to include mapa/mash.
|
||||||
.add(otherTotals.additional.total);
|
.add(otherTotals.additional.total);
|
||||||
|
|
||||||
// .add(Dinero({ amount: (job.towing_payable || 0) * 100 }))
|
// .add(Dinero({ amount: (job.towing_payable || 0) * 100 }))
|
||||||
// .add(Dinero({ amount: (job.storage_payable || 0) * 100 }));
|
// .add(Dinero({ amount: (job.storage_payable || 0) * 100 }));
|
||||||
|
|
||||||
@@ -522,7 +523,13 @@ function CalculateTaxesTotals(job, otherTotals) {
|
|||||||
|
|
||||||
let ret = {
|
let ret = {
|
||||||
subtotal: subtotal,
|
subtotal: subtotal,
|
||||||
federal_tax: subtotal.percentage((job.federal_tax_rate || 0) * 100),
|
federal_tax: subtotal
|
||||||
|
.percentage((job.federal_tax_rate || 0) * 100)
|
||||||
|
.add(
|
||||||
|
otherTotals.additional.pvrt.percentage(
|
||||||
|
(job.federal_tax_rate || 0) * 100
|
||||||
|
)
|
||||||
|
),
|
||||||
statePartsTax,
|
statePartsTax,
|
||||||
state_tax: statePartsTax
|
state_tax: statePartsTax
|
||||||
.add(
|
.add(
|
||||||
@@ -540,12 +547,14 @@ function CalculateTaxesTotals(job, otherTotals) {
|
|||||||
otherTotals.additional.storage.percentage((job.tax_str_rt || 0) * 100)
|
otherTotals.additional.storage.percentage((job.tax_str_rt || 0) * 100)
|
||||||
)
|
)
|
||||||
.add(additionalItemsTax),
|
.add(additionalItemsTax),
|
||||||
|
// .add(otherTotals.additional.pvrt),
|
||||||
local_tax: subtotal.percentage((job.local_tax_rate || 0) * 100),
|
local_tax: subtotal.percentage((job.local_tax_rate || 0) * 100),
|
||||||
};
|
};
|
||||||
ret.total_repairs = ret.subtotal
|
ret.total_repairs = ret.subtotal
|
||||||
.add(ret.federal_tax)
|
.add(ret.federal_tax)
|
||||||
.add(ret.state_tax)
|
.add(ret.state_tax)
|
||||||
.add(ret.local_tax);
|
.add(ret.local_tax)
|
||||||
|
.add(otherTotals.additional.pvrt);
|
||||||
|
|
||||||
ret.custPayable = {
|
ret.custPayable = {
|
||||||
deductible: Dinero({ amount: Math.round((job.ded_amt || 0) * 100) }) || 0,
|
deductible: Dinero({ amount: Math.round((job.ded_amt || 0) * 100) }) || 0,
|
||||||
|
|||||||
@@ -9,30 +9,84 @@ const axios = require("axios");
|
|||||||
const client = require("../graphql-client/graphql-client").client;
|
const client = require("../graphql-client/graphql-client").client;
|
||||||
const emailer = require("../email/sendemail");
|
const emailer = require("../email/sendemail");
|
||||||
const logger = require("../utils/logger");
|
const logger = require("../utils/logger");
|
||||||
|
const moment = require("moment-timezone");
|
||||||
exports.taskHandler = async (req, res) => {
|
exports.taskHandler = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { bodyshopid, query, variables, text, to, subject } = req.body;
|
const { bodyshopid, query, variables, text, to, subject, timezone } =
|
||||||
|
req.body;
|
||||||
//Run the query
|
//Run the query
|
||||||
|
|
||||||
|
//Check the variables to see if they are an object.
|
||||||
|
Object.keys(variables).forEach((key) => {
|
||||||
|
if (typeof variables[key] === "object") {
|
||||||
|
if (variables[key].function) {
|
||||||
|
variables[key] = functionMapper(variables[key].function, timezone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const response = await client.request(query, variables);
|
const response = await client.request(query, variables);
|
||||||
//Massage the data
|
//Massage the data
|
||||||
//Send the email
|
//Send the email
|
||||||
const rootElement = response[Object.keys(response)[0]]; //This element shoudl always be an array.
|
const rootElement = response[Object.keys(response)[0]]; //This element shoudl always be an array.
|
||||||
let converter = require("json-2-csv");
|
let converter = require("json-2-csv");
|
||||||
converter.json2csv(rootElement, (err, csv) => {
|
converter.json2csv(
|
||||||
if (err) {
|
rootElement,
|
||||||
res.status(500).json(err);
|
(err, csv) => {
|
||||||
}
|
if (err) {
|
||||||
|
res.status(500).json(err);
|
||||||
|
}
|
||||||
|
|
||||||
emailer.sendTaskEmail({
|
emailer.sendTaskEmail({
|
||||||
to,
|
to,
|
||||||
subject,
|
subject,
|
||||||
text,
|
text,
|
||||||
attachments: [{ filename: "query.csv", content: csv }],
|
attachments: [{ filename: "query.csv", content: csv }],
|
||||||
});
|
});
|
||||||
res.status(200).send(csv);
|
res.status(200).send(csv);
|
||||||
});
|
},
|
||||||
|
{ emptyFieldValue: "" }
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ error });
|
res.status(500).json({ error: error.message, stack: error.stackTrace });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isoformat = "YYYY-MM-DD";
|
||||||
|
function functionMapper(f, timezone) {
|
||||||
|
switch (f) {
|
||||||
|
case "date.today":
|
||||||
|
return moment().tz(timezone).format(isoformat);
|
||||||
|
case "date.now":
|
||||||
|
return moment().tz(timezone);
|
||||||
|
case "date.yesterday":
|
||||||
|
return moment().tz(timezone).subtract(1, "day").format(isoformat);
|
||||||
|
case "date.3daysago":
|
||||||
|
return moment().tz(timezone).subtract(3, "days").format(isoformat);
|
||||||
|
case "date.7daysago":
|
||||||
|
return moment().tz(timezone).subtract(7, "days").format(isoformat);
|
||||||
|
case "date.tomorrow":
|
||||||
|
return moment().tz(timezone).add(1, "day").format(isoformat);
|
||||||
|
case "date.3daysfromnow":
|
||||||
|
return moment().tz(timezone).add(3, "days").format(isoformat);
|
||||||
|
case "date.7daysfromnow":
|
||||||
|
return moment().tz(timezone).add(7, "days").format(isoformat);
|
||||||
|
case "date.yesterdaytz":
|
||||||
|
return moment().tz(timezone).subtract(1, "day");
|
||||||
|
case "date.3daysagotz":
|
||||||
|
return moment().tz(timezone).subtract(3, "days");
|
||||||
|
case "date.7daysagotz":
|
||||||
|
return moment().tz(timezone).subtract(7, "days");
|
||||||
|
case "date.tomorrowtz":
|
||||||
|
return moment().tz(timezone).add(1, "day");
|
||||||
|
case "date.3daysfromnowtz":
|
||||||
|
return moment().tz(timezone).add(3, "days");
|
||||||
|
case "date.7daysfromnowtz":
|
||||||
|
return moment().tz(timezone).add(7, "days");
|
||||||
|
|
||||||
|
case "date.now":
|
||||||
|
return moment().tz(timezone);
|
||||||
|
default:
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user