@@ -92,13 +92,14 @@
|
||||
}
|
||||
|
||||
.production-completion-1 {
|
||||
animation: production-completion-1-blinker 5s linear infinite;
|
||||
}
|
||||
@keyframes production-completion-1-blinker {
|
||||
50% {
|
||||
background: rgba(207, 12, 12, 0.555);
|
||||
}
|
||||
color: rgba(207, 12, 12, 0.8);
|
||||
|
||||
// animation: production-completion-1-blinker 1s linear infinite;
|
||||
}
|
||||
// @keyframes production-completion-1-blinker {
|
||||
// 50% {
|
||||
// }
|
||||
// }
|
||||
|
||||
.react-resizable {
|
||||
position: relative;
|
||||
|
||||
@@ -5,21 +5,25 @@ import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBreadcrumbs } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import GlobalSearch from "../global-search/global-search.component";
|
||||
import "./breadcrumbs.styles.scss";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
breadcrumbs: selectBreadcrumbs,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
export function BreadCrumbs({ breadcrumbs }) {
|
||||
export function BreadCrumbs({ breadcrumbs, bodyshop }) {
|
||||
return (
|
||||
<Row className="breadcrumb-container">
|
||||
<Col xs={24} sm={24} md={16}>
|
||||
<Breadcrumb separator=">">
|
||||
<Breadcrumb.Item>
|
||||
<Link to={`/manage`}>
|
||||
<HomeFilled />
|
||||
<HomeFilled />{" "}
|
||||
{(bodyshop && bodyshop.shopname && `(${bodyshop.shopname})`) ||
|
||||
""}
|
||||
</Link>
|
||||
</Breadcrumb.Item>
|
||||
{breadcrumbs.map((item) =>
|
||||
|
||||
@@ -64,9 +64,9 @@ export default function JobBillsTotalComponent({
|
||||
})
|
||||
);
|
||||
|
||||
const totalPartsSublet = Dinero(totals.parts.parts.total).add(
|
||||
Dinero(totals.parts.sublets.total)
|
||||
);
|
||||
const totalPartsSublet = Dinero(totals.parts.parts.total)
|
||||
.add(Dinero(totals.parts.sublets.total))
|
||||
.add(Dinero(totals.additional.additionalCosts));
|
||||
|
||||
const discrepancy = totalPartsSublet.subtract(billTotals);
|
||||
|
||||
|
||||
@@ -171,7 +171,12 @@ export function JobChecklistForm({
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
console.log(job,{
|
||||
removeFromProduction: true,
|
||||
actual_completion:
|
||||
job && job.actual_completion && moment(job.actual_completion),
|
||||
actual_delivery: job && job.actual_delivery && moment(job.actual_delivery),
|
||||
});
|
||||
return (
|
||||
<Card
|
||||
title={t("checklist.labels.checklist")}
|
||||
@@ -195,21 +200,26 @@ export function JobChecklistForm({
|
||||
addToProduction: true,
|
||||
allow_text_message: job.owner && job.owner.allow_text_message,
|
||||
scheduled_completion:
|
||||
(job && job.scheduled_completion) ||
|
||||
(job.labbrs && job.larhrs
|
||||
? moment().businessAdd(
|
||||
(job.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
job.larhrs.aggregate.sum.mod_lb_hrs) /
|
||||
bodyshop.target_touchtime,
|
||||
"days"
|
||||
)
|
||||
: null),
|
||||
scheduled_delivery: job && job.scheduled_delivery,
|
||||
(job &&
|
||||
job.scheduled_completion &&
|
||||
moment(job.scheduled_completion)) ||
|
||||
(job &&
|
||||
job.labhrs &&
|
||||
job.larhrs &&
|
||||
moment().businessAdd(
|
||||
(job.labhrs.aggregate.sum.mod_lb_hrs ||
|
||||
0 + job.larhrs.aggregate.sum.mod_lb_hrs ||
|
||||
0) / bodyshop.target_touchtime,
|
||||
"days"
|
||||
)),
|
||||
scheduled_delivery: job && moment(job.scheduled_delivery),
|
||||
}),
|
||||
...(type === "deliver" && {
|
||||
removeFromProduction: true,
|
||||
actual_completion: job && job.actual_completion,
|
||||
actual_delivery: job && job.actual_delivery,
|
||||
actual_completion:
|
||||
job && job.actual_completion && moment(job.actual_completion),
|
||||
actual_delivery:
|
||||
job && job.actual_delivery && moment(job.actual_delivery),
|
||||
}),
|
||||
...formItems
|
||||
.filter((fi) => fi.value)
|
||||
|
||||
@@ -18,7 +18,8 @@ export default function JobReconciliationModalComponent({ job, bills }) {
|
||||
.flat() || [];
|
||||
|
||||
const jobLineData = job.joblines.filter(
|
||||
(j) => j.part_type !== null && j.part_type !== "PAE"
|
||||
(j) =>
|
||||
(j.part_type !== null && j.part_type !== "PAE") || IsAdditionalCost(j)
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -50,3 +51,20 @@ export default function JobReconciliationModalComponent({ job, bills }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function IsAdditionalCost(jobLine) {
|
||||
//May be able to use db_ref here to help.
|
||||
//936012 is Haz Waste Dispoal
|
||||
//936008 is Paint/Materials
|
||||
//936007 is Shop/Materials
|
||||
|
||||
//Remove paint and shop mat lines. They're calculated under rates.
|
||||
const isPaintOrShopMat =
|
||||
jobLine.db_ref === "936008" || jobLine.db_ref === "936007";
|
||||
|
||||
return (
|
||||
(jobLine.lbr_op === "OP13" || //Added to resolve manual job lines coming into other totals because they have no reference.
|
||||
(jobLine.db_ref && jobLine.db_ref.startsWith("9360"))) &&
|
||||
!isPaintOrShopMat
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,9 +8,6 @@ import { DateFormatter } from "../../utils/DateFormatter";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const OneCalendarDay = 60 * 60 * 24 * 1000;
|
||||
const Now = new Date();
|
||||
|
||||
export default function ProductionListDate({ record, field, time }) {
|
||||
const [updateAlert] = useMutation(UPDATE_JOB);
|
||||
const [visible, setVisible] = useState(false);
|
||||
@@ -72,17 +69,13 @@ export default function ProductionListDate({ record, field, time }) {
|
||||
style={{
|
||||
height: "19px",
|
||||
}}
|
||||
className={
|
||||
!!record[field] && moment().isSame(moment(record[field]), "day")
|
||||
? "production-completion-1"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
<DateFormatter
|
||||
bordered={false}
|
||||
className={
|
||||
!!record[field] && new Date(record[field]) - Now < OneCalendarDay
|
||||
? "production-completion-1"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
{record[field]}
|
||||
</DateFormatter>
|
||||
<DateFormatter bordered={false}>{record[field]}</DateFormatter>
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
@@ -262,6 +262,9 @@ export const QUERY_DELIVER_CHECKLIST = gql`
|
||||
jobs_by_pk(id: $jobId) {
|
||||
id
|
||||
ro_number
|
||||
actual_completion
|
||||
actual_delivery
|
||||
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -69,6 +69,7 @@ export function JobsDeliverContainer({
|
||||
checklistConfig={
|
||||
(data && data.bodyshops_by_pk.deliverchecklist) || {}
|
||||
}
|
||||
job={data ? data.jobs_by_pk : {}}
|
||||
/>
|
||||
</div>
|
||||
</RbacWrapper>
|
||||
|
||||
@@ -1030,6 +1030,7 @@ exports.QUERY_JOB_COSTING_DETAILS = ` query QUERY_JOB_COSTING_DETAILS($id: uuid!
|
||||
status
|
||||
ca_bc_pvrt
|
||||
ca_customer_gst
|
||||
dms_allocation
|
||||
joblines(where: { removed: { _eq: false } }) {
|
||||
id
|
||||
db_ref
|
||||
@@ -1073,11 +1074,14 @@ exports.QUERY_JOB_COSTING_DETAILS = ` query QUERY_JOB_COSTING_DETAILS($id: uuid!
|
||||
actualhrs
|
||||
productivehrs
|
||||
flat_rate
|
||||
ciecacode
|
||||
}
|
||||
bodyshop{
|
||||
id
|
||||
md_responsibility_centers
|
||||
jc_hourly_rates
|
||||
cdk_dealerid
|
||||
pbs_serialnumber
|
||||
}
|
||||
}
|
||||
}`;
|
||||
@@ -1133,6 +1137,7 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT
|
||||
status
|
||||
ca_bc_pvrt
|
||||
ca_customer_gst
|
||||
dms_allocation
|
||||
joblines(where: {removed: {_eq: false}}) {
|
||||
id
|
||||
db_ref
|
||||
@@ -1176,11 +1181,14 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT
|
||||
actualhrs
|
||||
productivehrs
|
||||
flat_rate
|
||||
ciecacode
|
||||
}
|
||||
bodyshop {
|
||||
id
|
||||
md_responsibility_centers
|
||||
jc_hourly_rates
|
||||
cdk_dealerid
|
||||
pbs_serialnumber
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ function GenerateCostingData(job) {
|
||||
val.profitcenter_labor || defaultProfits[val.mod_lbr_ty] || "?";
|
||||
|
||||
if (laborProfitCenter === "?")
|
||||
console.log("Unknown type", val.mod_lbr_ty);
|
||||
console.log("Unknown type", val.line_desc, val.mod_lbr_ty);
|
||||
|
||||
const rateName = `rate_${(val.mod_lbr_ty || "").toLowerCase()}`;
|
||||
const laborAmount = Dinero({
|
||||
@@ -285,11 +285,12 @@ function GenerateCostingData(job) {
|
||||
val.profitcenter_part || defaultProfits[val.part_type] || "?";
|
||||
|
||||
if (partsProfitCenter === "?")
|
||||
console.log("Unknown type", val.part_type);
|
||||
console.log("Unknown type", val.line_desc, val.part_type);
|
||||
|
||||
if (!partsProfitCenter)
|
||||
console.log(
|
||||
"Unknown cost/profit center mapping for parts.",
|
||||
val.line_desc,
|
||||
val.part_type
|
||||
);
|
||||
const partsAmount = Dinero({
|
||||
@@ -298,13 +299,13 @@ function GenerateCostingData(job) {
|
||||
.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)
|
||||
? 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.parts[partsProfitCenter])
|
||||
acc.parts[partsProfitCenter] = Dinero();
|
||||
@@ -322,7 +323,7 @@ function GenerateCostingData(job) {
|
||||
"?";
|
||||
|
||||
if (partsProfitCenter === "?") {
|
||||
console.log("Unknown type", val.part_type);
|
||||
console.log("Unknown type", val.line_desc, val.part_type);
|
||||
} else {
|
||||
const partsAmount = Dinero({
|
||||
amount: Math.round((val.act_price || 0) * 100),
|
||||
@@ -330,13 +331,13 @@ function GenerateCostingData(job) {
|
||||
.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)
|
||||
? 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.parts[partsProfitCenter])
|
||||
@@ -372,21 +373,41 @@ function GenerateCostingData(job) {
|
||||
);
|
||||
}
|
||||
|
||||
//Is it a DMS Setup?
|
||||
const selectedDmsAllocationConfig =
|
||||
job.bodyshop.md_responsibility_centers.dms_defaults.find(
|
||||
(d) => d.name === job.dms_allocation
|
||||
) || job.bodyshop.md_responsibility_centers.defaults;
|
||||
|
||||
const billTotalsByCostCenters = job.bills.reduce((bill_acc, bill_val) => {
|
||||
//At the bill level.
|
||||
bill_val.billlines.map((line_val) => {
|
||||
//At the bill line level.
|
||||
//console.log("JobCostingPartsTable -> line_val", line_val);
|
||||
if (!bill_acc[line_val.cost_center])
|
||||
bill_acc[line_val.cost_center] = Dinero();
|
||||
if (job.bodyshop.pbs_serialnumber || job.bodyshop.cdk_dealerid) {
|
||||
if (!bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]])
|
||||
bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] =
|
||||
Dinero();
|
||||
|
||||
bill_acc[line_val.cost_center] = bill_acc[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)
|
||||
);
|
||||
bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] =
|
||||
bill_acc[selectedDmsAllocationConfig.costs[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 {
|
||||
if (!bill_acc[line_val.cost_center])
|
||||
bill_acc[line_val.cost_center] = Dinero();
|
||||
|
||||
bill_acc[line_val.cost_center] = bill_acc[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)
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
@@ -443,20 +464,38 @@ function GenerateCostingData(job) {
|
||||
const ticketTotalsByCostCenter = job.timetickets.reduce(
|
||||
(ticket_acc, ticket_val) => {
|
||||
//At the invoice level.
|
||||
if (!ticket_acc[ticket_val.cost_center])
|
||||
ticket_acc[ticket_val.cost_center] = Dinero();
|
||||
|
||||
ticket_acc[ticket_val.cost_center] = ticket_acc[
|
||||
ticket_val.cost_center
|
||||
].add(
|
||||
Dinero({
|
||||
amount: Math.round((ticket_val.rate || 0) * 100),
|
||||
}).multiply(
|
||||
ticket_val.flat_rate
|
||||
? ticket_val.productivehrs || ticket_val.actualhrs || 0
|
||||
: ticket_val.actualhrs || ticket_val.productivehrs || 0
|
||||
) //Should base this on the employee.
|
||||
);
|
||||
if (job.bodyshop.pbs_serialnumber || job.bodyshop.cdk_dealerid) {
|
||||
if (!ticket_acc[selectedDmsAllocationConfig.costs[ticket_val.ciecacode]])
|
||||
ticket_acc[selectedDmsAllocationConfig.costs[ticket_val.ciecacode]] =
|
||||
Dinero();
|
||||
|
||||
ticket_acc[selectedDmsAllocationConfig.costs[ticket_val.ciecacode]] =
|
||||
ticket_acc[selectedDmsAllocationConfig.costs[ticket_val.ciecacode]].add(
|
||||
Dinero({
|
||||
amount: Math.round((ticket_val.rate || 0) * 100),
|
||||
}).multiply(
|
||||
ticket_val.flat_rate
|
||||
? ticket_val.productivehrs || ticket_val.actualhrs || 0
|
||||
: ticket_val.actualhrs || ticket_val.productivehrs || 0
|
||||
) //Should base this on the employee.
|
||||
);
|
||||
} else {
|
||||
if (!ticket_acc[ticket_val.cost_center])
|
||||
ticket_acc[ticket_val.cost_center] = Dinero();
|
||||
|
||||
ticket_acc[ticket_val.cost_center] = ticket_acc[
|
||||
ticket_val.cost_center
|
||||
].add(
|
||||
Dinero({
|
||||
amount: Math.round((ticket_val.rate || 0) * 100),
|
||||
}).multiply(
|
||||
ticket_val.flat_rate
|
||||
? ticket_val.productivehrs || ticket_val.actualhrs || 0
|
||||
: ticket_val.actualhrs || ticket_val.productivehrs || 0
|
||||
) //Should base this on the employee.
|
||||
);
|
||||
}
|
||||
|
||||
return ticket_acc;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user