WIP for Job Costing BOD-192
This commit is contained in:
@@ -12169,6 +12169,27 @@
|
|||||||
</concept_node>
|
</concept_node>
|
||||||
</children>
|
</children>
|
||||||
</folder_node>
|
</folder_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>cost</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<folder_node>
|
<folder_node>
|
||||||
<name>create</name>
|
<name>create</name>
|
||||||
<children>
|
<children>
|
||||||
@@ -12384,6 +12405,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>estimated</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>existing_jobs</name>
|
<name>existing_jobs</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -12426,6 +12468,48 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>gpdollars</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>gppercent</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>hrs_claimed</name>
|
<name>hrs_claimed</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -12510,6 +12594,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>jobcosting</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>laborallocations</name>
|
<name>laborallocations</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
|
|||||||
@@ -108,12 +108,17 @@ if (process.env.NODE_ENV === "development") {
|
|||||||
|
|
||||||
middlewares.push(retryLink.concat(errorLink.concat(authLink.concat(link))));
|
middlewares.push(retryLink.concat(errorLink.concat(authLink.concat(link))));
|
||||||
|
|
||||||
const cache = new InMemoryCache();
|
const cache = new InMemoryCache({});
|
||||||
|
|
||||||
export const client = new ApolloClient({
|
export const client = new ApolloClient({
|
||||||
link: ApolloLink.from(middlewares),
|
link: ApolloLink.from(middlewares),
|
||||||
cache,
|
cache,
|
||||||
connectToDevTools: process.env.NODE_ENV !== "production",
|
connectToDevTools: process.env.NODE_ENV !== "production",
|
||||||
|
defaultOptions: {
|
||||||
|
watchQuery: {
|
||||||
|
fetchPolicy: "cache-and-network",
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function AppContainer() {
|
export default function AppContainer() {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import React from "react";
|
import React, { forwardRef } from "react";
|
||||||
import DatePicker from "react-datepicker";
|
import DatePicker from "react-datepicker";
|
||||||
import "react-datepicker/dist/react-datepicker.css";
|
import "react-datepicker/dist/react-datepicker.css";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
//To be used as a form element only.
|
//To be used as a form element only.
|
||||||
|
|
||||||
const DateTimePicker = ({ value, onChange, onBlur }) => {
|
const DateTimePicker = ({ value, onChange, onBlur }, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleChange = (value) => {
|
const handleChange = (value) => {
|
||||||
@@ -30,4 +30,4 @@ const DateTimePicker = ({ value, onChange, onBlur }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DateTimePicker;
|
export default forwardRef(DateTimePicker);
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import React from "react";
|
||||||
|
import JobCostingPartsTable from "../job-costing-parts-table/job-costing-parts-table.component";
|
||||||
|
import { Row, Col } from "antd";
|
||||||
|
|
||||||
|
const colSpan = {
|
||||||
|
md: { span: 24 },
|
||||||
|
lg: { span: 12 },
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function JobCostingModalComponent({ job }) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Row gutter={[32, 32]}>
|
||||||
|
<Col {...colSpan}>
|
||||||
|
<JobCostingPartsTable job={job} />
|
||||||
|
</Col>
|
||||||
|
<Col {...colSpan}></Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { useQuery } from "@apollo/react-hooks";
|
||||||
|
import { Modal } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { QUERY_JOB_COSTING_DETAILS } from "../../graphql/jobs.queries";
|
||||||
|
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||||
|
import { selectJobCosting } from "../../redux/modals/modals.selectors";
|
||||||
|
import AlertComponent from "../alert/alert.component";
|
||||||
|
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||||
|
import JobCostingModalComponent from "./job-costing-modal.component";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
jobCostingModal: selectJobCosting,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
toggleModalVisible: () => dispatch(toggleModalVisible("jobCosting")),
|
||||||
|
});
|
||||||
|
|
||||||
|
export function JobCostingModalContainer({
|
||||||
|
jobCostingModal,
|
||||||
|
toggleModalVisible,
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { visible, context, actions } = jobCostingModal;
|
||||||
|
const { jobId } = context;
|
||||||
|
|
||||||
|
const { loading, error, data } = useQuery(QUERY_JOB_COSTING_DETAILS, {
|
||||||
|
variables: { id: jobId },
|
||||||
|
skip: !jobId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
visible={visible}
|
||||||
|
title={t("jobs.labels.jobcosting")}
|
||||||
|
onCancel={() => toggleModalVisible()}
|
||||||
|
width='90%'
|
||||||
|
destroyOnClose
|
||||||
|
forceRender>
|
||||||
|
{error ? <AlertComponent message={error.message} type='error' /> : null}
|
||||||
|
{loading ? (
|
||||||
|
<LoadingSpinner loading={loading} />
|
||||||
|
) : (
|
||||||
|
<JobCostingModalComponent job={data && data.jobs_by_pk} />
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(JobCostingModalContainer);
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import "./job-costing-parts-table.styles.scss";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import Dinero from "dinero.js";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop,
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
|
});
|
||||||
|
|
||||||
|
export function JobCostingPartsTable({ bodyshop, job }) {
|
||||||
|
console.log("JobCostingPartsTable -> job", job);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const defaultProfits = bodyshop.md_responsibility_centers.defaults.profits;
|
||||||
|
console.log("JobCostingPartsTable -> defaultProfits", defaultProfits);
|
||||||
|
const defaultCosts = bodyshop.md_responsibility_centers.defaults.costs;
|
||||||
|
|
||||||
|
//Need to get the sums of the job lines by cost center.
|
||||||
|
const jobLineTotalsByProfitCenter = job.joblines.reduce(
|
||||||
|
(acc, val) => {
|
||||||
|
const laborProfitCenter = defaultProfits[val.mod_lbr_ty];
|
||||||
|
if (!!!laborProfitCenter)
|
||||||
|
console.log(
|
||||||
|
"Unknown cost/profit center mapping for labor.",
|
||||||
|
val.mod_lbr_ty
|
||||||
|
);
|
||||||
|
|
||||||
|
const rateName = `rate_${(val.mod_lbr_ty || "").toLowerCase()}`;
|
||||||
|
const laborAmount = Dinero({
|
||||||
|
amount: Math.round((job[rateName] || 0) * 100),
|
||||||
|
}).multiply(val.mod_lb_hrs || 0);
|
||||||
|
if (!!!acc.labor[laborProfitCenter])
|
||||||
|
acc.labor[laborProfitCenter] = Dinero();
|
||||||
|
acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter].add(
|
||||||
|
laborAmount
|
||||||
|
);
|
||||||
|
|
||||||
|
const partsProfitCenter = defaultProfits[val.part_type];
|
||||||
|
if (!!!partsProfitCenter)
|
||||||
|
console.log(
|
||||||
|
"Unknown cost/profit center mapping for parts.",
|
||||||
|
val.part_type
|
||||||
|
);
|
||||||
|
const partsAmount = Dinero({
|
||||||
|
amount: Math.round((val.act_price || 0) * 100),
|
||||||
|
}).multiply(val.part_qty || 1);
|
||||||
|
if (!!!acc.parts[partsProfitCenter])
|
||||||
|
acc.parts[partsProfitCenter] = Dinero();
|
||||||
|
acc.parts[partsProfitCenter] = acc.parts[partsProfitCenter].add(
|
||||||
|
partsAmount
|
||||||
|
);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ parts: {}, labor: {} }
|
||||||
|
);
|
||||||
|
|
||||||
|
const invoiceTotalsByProfitCenter = job.invoices.reduce(
|
||||||
|
(inv_acc, inv_val) => {
|
||||||
|
//At the invoice level.
|
||||||
|
const t = inv_val.invoicelines.map((line_val) => {
|
||||||
|
//At the invoice line level.
|
||||||
|
console.log("JobCostingPartsTable -> line_val", line_val);
|
||||||
|
if (!!!inv_acc[line_val.cost_center])
|
||||||
|
inv_acc[line_val.cost_center] = Dinero();
|
||||||
|
|
||||||
|
inv_acc[line_val.cost_center] = inv_acc[line_val.cost_center].add(
|
||||||
|
Dinero({
|
||||||
|
amount: Math.round((line_val.actual_cost || 0) * 100),
|
||||||
|
})
|
||||||
|
.multiply(line_val.quantity)
|
||||||
|
.multiply(inv_val.is_credit_memo ? -1 : 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return inv_acc;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
"JobCostingPartsTable -> invoiceTotalsByProfitCenter",
|
||||||
|
invoiceTotalsByProfitCenter
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='job-costing-parts-table-container'>
|
||||||
|
<table className='job-costing-parts-table'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{t("bodyshop.fields.responsibilitycenter")}</th>
|
||||||
|
<th>{t("jobs.labels.estimated")}</th>
|
||||||
|
<th>{t("jobs.labels.cost")}</th>
|
||||||
|
<th>{t("jobs.labels.gpdollars")}</th>
|
||||||
|
<th>{t("jobs.labels.gppercent")}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Cost Center</td>
|
||||||
|
<td>$12345.45</td>
|
||||||
|
<td>$123.45</td>
|
||||||
|
<td>$1234.00</td>
|
||||||
|
<td>50.58%</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(JobCostingPartsTable);
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
.job-costing-parts-table-container {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-costing-parts-table {
|
||||||
|
border: black;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
@@ -143,11 +143,20 @@ export function JobLinesComponent({
|
|||||||
</CurrencyFormatter>
|
</CurrencyFormatter>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t("joblines.fields.mod_lbr_ty"),
|
||||||
|
dataIndex: "mod_lbr_ty",
|
||||||
|
key: "mod_lbr_ty",
|
||||||
|
|
||||||
|
sorter: (a, b) => alphaSort(a.mod_lbr_ty, b.mod_lbr_ty),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "mod_lbr_ty" && state.sortedInfo.order,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t("joblines.fields.mod_lb_hrs"),
|
title: t("joblines.fields.mod_lb_hrs"),
|
||||||
dataIndex: "mod_lb_hrs",
|
dataIndex: "mod_lb_hrs",
|
||||||
key: "mod_lb_hrs",
|
key: "mod_lb_hrs",
|
||||||
responsive: ["lg"],
|
|
||||||
sorter: (a, b) => a.mod_lb_hrs - b.mod_lb_hrs,
|
sorter: (a, b) => a.mod_lb_hrs - b.mod_lb_hrs,
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "mod_lb_hrs" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "mod_lb_hrs" && state.sortedInfo.order,
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
dispatch(setModalContext({ context: context, modal: "invoiceEnter" })),
|
dispatch(setModalContext({ context: context, modal: "invoiceEnter" })),
|
||||||
setPaymentContext: (context) =>
|
setPaymentContext: (context) =>
|
||||||
dispatch(setModalContext({ context: context, modal: "payment" })),
|
dispatch(setModalContext({ context: context, modal: "payment" })),
|
||||||
|
setJobCostingContext: (context) =>
|
||||||
|
dispatch(setModalContext({ context: context, modal: "jobCosting" })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function JobsDetailHeaderActions({
|
export function JobsDetailHeaderActions({
|
||||||
@@ -33,6 +35,7 @@ export function JobsDetailHeaderActions({
|
|||||||
setScheduleContext,
|
setScheduleContext,
|
||||||
setInvoiceEnterContext,
|
setInvoiceEnterContext,
|
||||||
setPaymentContext,
|
setPaymentContext,
|
||||||
|
setJobCostingContext,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
@@ -123,6 +126,20 @@ export function JobsDetailHeaderActions({
|
|||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<JobsDetaiLheaderCsi job={job} />
|
<JobsDetaiLheaderCsi job={job} />
|
||||||
|
<Menu.Item
|
||||||
|
key='jobcosting'
|
||||||
|
onClick={() => {
|
||||||
|
logImEXEvent("job_header_job_costing");
|
||||||
|
|
||||||
|
setJobCostingContext({
|
||||||
|
actions: { refetch: refetch },
|
||||||
|
context: {
|
||||||
|
jobId: job.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}>
|
||||||
|
{t("jobs.labels.jobcosting")}
|
||||||
|
</Menu.Item>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -130,6 +130,96 @@ export const SUBSCRIPTION_JOBS_IN_PRODUCTION = gql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const QUERY_JOB_COSTING_DETAILS = gql`
|
||||||
|
query QUERY_JOB_COSTING_DETAILS($id: uuid!) {
|
||||||
|
jobs_by_pk(id: $id) {
|
||||||
|
est_number
|
||||||
|
ro_number
|
||||||
|
clm_total
|
||||||
|
id
|
||||||
|
ded_amt
|
||||||
|
ded_status
|
||||||
|
depreciation_taxes
|
||||||
|
federal_tax_payable
|
||||||
|
other_amount_payable
|
||||||
|
towing_payable
|
||||||
|
storage_payable
|
||||||
|
adjustment_bottom_line
|
||||||
|
federal_tax_rate
|
||||||
|
state_tax_rate
|
||||||
|
local_tax_rate
|
||||||
|
tax_tow_rt
|
||||||
|
tax_str_rt
|
||||||
|
tax_paint_mat_rt
|
||||||
|
tax_sub_rt
|
||||||
|
tax_lbr_rt
|
||||||
|
tax_levies_rt
|
||||||
|
parts_tax_rates
|
||||||
|
job_totals
|
||||||
|
labor_rate_desc
|
||||||
|
rate_atp
|
||||||
|
rate_la1
|
||||||
|
rate_la2
|
||||||
|
rate_la3
|
||||||
|
rate_la4
|
||||||
|
rate_laa
|
||||||
|
rate_lab
|
||||||
|
rate_lad
|
||||||
|
rate_lae
|
||||||
|
rate_laf
|
||||||
|
rate_lag
|
||||||
|
rate_lam
|
||||||
|
rate_lar
|
||||||
|
rate_las
|
||||||
|
rate_lau
|
||||||
|
rate_ma2s
|
||||||
|
rate_ma2t
|
||||||
|
rate_ma3s
|
||||||
|
rate_mabl
|
||||||
|
rate_macs
|
||||||
|
rate_mahw
|
||||||
|
rate_mapa
|
||||||
|
rate_mash
|
||||||
|
rate_matd
|
||||||
|
actual_in
|
||||||
|
status
|
||||||
|
joblines {
|
||||||
|
id
|
||||||
|
unq_seq
|
||||||
|
line_ind
|
||||||
|
tax_part
|
||||||
|
line_desc
|
||||||
|
prt_dsmk_p
|
||||||
|
prt_dsmk_m
|
||||||
|
part_type
|
||||||
|
oem_partno
|
||||||
|
db_price
|
||||||
|
act_price
|
||||||
|
part_qty
|
||||||
|
mod_lbr_ty
|
||||||
|
db_hrs
|
||||||
|
mod_lb_hrs
|
||||||
|
lbr_op
|
||||||
|
lbr_amt
|
||||||
|
op_code_desc
|
||||||
|
}
|
||||||
|
invoices {
|
||||||
|
id
|
||||||
|
federal_tax_rate
|
||||||
|
local_tax_rate
|
||||||
|
state_tax_rate
|
||||||
|
is_credit_memo
|
||||||
|
invoicelines {
|
||||||
|
actual_cost
|
||||||
|
cost_center
|
||||||
|
id
|
||||||
|
quantity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export const GET_JOB_BY_PK = gql`
|
export const GET_JOB_BY_PK = gql`
|
||||||
query GET_JOB_BY_PK($id: uuid!) {
|
query GET_JOB_BY_PK($id: uuid!) {
|
||||||
jobs_by_pk(id: $id) {
|
jobs_by_pk(id: $id) {
|
||||||
|
|||||||
@@ -78,6 +78,9 @@ const InvoicesListPage = lazy(() =>
|
|||||||
import("../invoices/invoices.page.container")
|
import("../invoices/invoices.page.container")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const JobCostingModal = lazy(() =>
|
||||||
|
import("../../components/job-costing-modal/job-costing-modal.container")
|
||||||
|
);
|
||||||
const EnterInvoiceModalContainer = lazy(() =>
|
const EnterInvoiceModalContainer = lazy(() =>
|
||||||
import("../../components/invoice-enter-modal/invoice-enter-modal.container")
|
import("../../components/invoice-enter-modal/invoice-enter-modal.container")
|
||||||
);
|
);
|
||||||
@@ -162,6 +165,7 @@ export function Manage({ match, conflict }) {
|
|||||||
<LoadingSpinner message={t("general.labels.loadingapp")} />
|
<LoadingSpinner message={t("general.labels.loadingapp")} />
|
||||||
}>
|
}>
|
||||||
<EnterInvoiceModalContainer />
|
<EnterInvoiceModalContainer />
|
||||||
|
<JobCostingModal />
|
||||||
<EmailOverlayContainer />
|
<EmailOverlayContainer />
|
||||||
<TimeTicketModalContainer />
|
<TimeTicketModalContainer />
|
||||||
<PrintCenterModalContainer />
|
<PrintCenterModalContainer />
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const INITIAL_STATE = {
|
|||||||
printCenter: { ...baseModal },
|
printCenter: { ...baseModal },
|
||||||
reconciliation: { ...baseModal },
|
reconciliation: { ...baseModal },
|
||||||
payment: { ...baseModal },
|
payment: { ...baseModal },
|
||||||
|
jobCosting: { ...baseModal },
|
||||||
};
|
};
|
||||||
|
|
||||||
const modalsReducer = (state = INITIAL_STATE, action) => {
|
const modalsReducer = (state = INITIAL_STATE, action) => {
|
||||||
|
|||||||
@@ -50,3 +50,8 @@ export const selectPayment = createSelector(
|
|||||||
[selectModals],
|
[selectModals],
|
||||||
(modals) => modals.payment
|
(modals) => modals.payment
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const selectJobCosting = createSelector(
|
||||||
|
[selectModals],
|
||||||
|
(modals) => modals.jobCosting
|
||||||
|
);
|
||||||
|
|||||||
@@ -753,6 +753,7 @@
|
|||||||
"totals": "Totals",
|
"totals": "Totals",
|
||||||
"vehicle": "Vehicle"
|
"vehicle": "Vehicle"
|
||||||
},
|
},
|
||||||
|
"cost": "Cost",
|
||||||
"create": {
|
"create": {
|
||||||
"jobinfo": "Job Info",
|
"jobinfo": "Job Info",
|
||||||
"newowner": "Create a new Owner instead. ",
|
"newowner": "Create a new Owner instead. ",
|
||||||
@@ -765,12 +766,16 @@
|
|||||||
"documents": "Documents",
|
"documents": "Documents",
|
||||||
"duplicateconfirm": "Are you sure you want to duplicate this job? Some elements of this job will not be duplicated.",
|
"duplicateconfirm": "Are you sure you want to duplicate this job? Some elements of this job will not be duplicated.",
|
||||||
"employeeassignments": "Employee Assignments",
|
"employeeassignments": "Employee Assignments",
|
||||||
|
"estimated": "Estimated",
|
||||||
"existing_jobs": "Existing Jobs",
|
"existing_jobs": "Existing Jobs",
|
||||||
"federal_tax_amt": "Federal Taxes",
|
"federal_tax_amt": "Federal Taxes",
|
||||||
|
"gpdollars": "$ G.P.",
|
||||||
|
"gppercent": "% G.P.",
|
||||||
"hrs_claimed": "Hours Claimed",
|
"hrs_claimed": "Hours Claimed",
|
||||||
"hrs_total": "Hours Total",
|
"hrs_total": "Hours Total",
|
||||||
"inproduction": "In Production",
|
"inproduction": "In Production",
|
||||||
"job": "Job Details",
|
"job": "Job Details",
|
||||||
|
"jobcosting": "Job Costing",
|
||||||
"laborallocations": "Labor Allocations",
|
"laborallocations": "Labor Allocations",
|
||||||
"lines": "Estimate Lines",
|
"lines": "Estimate Lines",
|
||||||
"local_tax_amt": "Local Taxes",
|
"local_tax_amt": "Local Taxes",
|
||||||
|
|||||||
@@ -753,6 +753,7 @@
|
|||||||
"totals": "Totales",
|
"totals": "Totales",
|
||||||
"vehicle": "Vehículo"
|
"vehicle": "Vehículo"
|
||||||
},
|
},
|
||||||
|
"cost": "",
|
||||||
"create": {
|
"create": {
|
||||||
"jobinfo": "",
|
"jobinfo": "",
|
||||||
"newowner": "",
|
"newowner": "",
|
||||||
@@ -765,12 +766,16 @@
|
|||||||
"documents": "documentos",
|
"documents": "documentos",
|
||||||
"duplicateconfirm": "",
|
"duplicateconfirm": "",
|
||||||
"employeeassignments": "",
|
"employeeassignments": "",
|
||||||
|
"estimated": "",
|
||||||
"existing_jobs": "Empleos existentes",
|
"existing_jobs": "Empleos existentes",
|
||||||
"federal_tax_amt": "",
|
"federal_tax_amt": "",
|
||||||
|
"gpdollars": "",
|
||||||
|
"gppercent": "",
|
||||||
"hrs_claimed": "",
|
"hrs_claimed": "",
|
||||||
"hrs_total": "",
|
"hrs_total": "",
|
||||||
"inproduction": "",
|
"inproduction": "",
|
||||||
"job": "",
|
"job": "",
|
||||||
|
"jobcosting": "",
|
||||||
"laborallocations": "",
|
"laborallocations": "",
|
||||||
"lines": "Líneas estimadas",
|
"lines": "Líneas estimadas",
|
||||||
"local_tax_amt": "",
|
"local_tax_amt": "",
|
||||||
|
|||||||
@@ -753,6 +753,7 @@
|
|||||||
"totals": "Totaux",
|
"totals": "Totaux",
|
||||||
"vehicle": "Véhicule"
|
"vehicle": "Véhicule"
|
||||||
},
|
},
|
||||||
|
"cost": "",
|
||||||
"create": {
|
"create": {
|
||||||
"jobinfo": "",
|
"jobinfo": "",
|
||||||
"newowner": "",
|
"newowner": "",
|
||||||
@@ -765,12 +766,16 @@
|
|||||||
"documents": "Les documents",
|
"documents": "Les documents",
|
||||||
"duplicateconfirm": "",
|
"duplicateconfirm": "",
|
||||||
"employeeassignments": "",
|
"employeeassignments": "",
|
||||||
|
"estimated": "",
|
||||||
"existing_jobs": "Emplois existants",
|
"existing_jobs": "Emplois existants",
|
||||||
"federal_tax_amt": "",
|
"federal_tax_amt": "",
|
||||||
|
"gpdollars": "",
|
||||||
|
"gppercent": "",
|
||||||
"hrs_claimed": "",
|
"hrs_claimed": "",
|
||||||
"hrs_total": "",
|
"hrs_total": "",
|
||||||
"inproduction": "",
|
"inproduction": "",
|
||||||
"job": "",
|
"job": "",
|
||||||
|
"jobcosting": "",
|
||||||
"laborallocations": "",
|
"laborallocations": "",
|
||||||
"lines": "Estimer les lignes",
|
"lines": "Estimer les lignes",
|
||||||
"local_tax_amt": "",
|
"local_tax_amt": "",
|
||||||
|
|||||||
Reference in New Issue
Block a user