Merged in release/2022-02-18 (pull request #405)

Updated task scheduler.

Approved-by: Patrick Fic
This commit is contained in:
Patrick Fic
2022-02-18 20:59:57 +00:00
24 changed files with 494 additions and 74 deletions

View File

@@ -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>

View File

@@ -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,
}, },

View File

@@ -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 (

View File

@@ -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>
); );

View File

@@ -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")}

View File

@@ -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]);

View File

@@ -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 = [
{ {

View File

@@ -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
/> />
), ),
}, },

View File

@@ -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"
/> />
)} )}

View File

@@ -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%"}

View File

@@ -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>

View File

@@ -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"),

View File

@@ -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,

View File

@@ -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>
);
}

View File

@@ -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
} }
} }
} }

View File

@@ -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}>

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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);
} }

View File

@@ -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"

View File

@@ -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

View File

@@ -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,

View File

@@ -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;
}
}