Merged in release/2022-01-21 (pull request #350)

Release/2022 01 21
This commit is contained in:
Patrick Fic
2022-01-17 21:21:33 +00:00
10 changed files with 155 additions and 78 deletions

View File

@@ -92,13 +92,14 @@
} }
.production-completion-1 { .production-completion-1 {
animation: production-completion-1-blinker 5s linear infinite; color: rgba(207, 12, 12, 0.8);
}
@keyframes production-completion-1-blinker { // animation: production-completion-1-blinker 1s linear infinite;
50% {
background: rgba(207, 12, 12, 0.555);
}
} }
// @keyframes production-completion-1-blinker {
// 50% {
// }
// }
.react-resizable { .react-resizable {
position: relative; position: relative;

View File

@@ -5,21 +5,25 @@ import { connect } from "react-redux";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBreadcrumbs } from "../../redux/application/application.selectors"; import { selectBreadcrumbs } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import GlobalSearch from "../global-search/global-search.component"; import GlobalSearch from "../global-search/global-search.component";
import "./breadcrumbs.styles.scss"; import "./breadcrumbs.styles.scss";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
breadcrumbs: selectBreadcrumbs, breadcrumbs: selectBreadcrumbs,
bodyshop: selectBodyshop,
}); });
export function BreadCrumbs({ breadcrumbs }) { export function BreadCrumbs({ breadcrumbs, bodyshop }) {
return ( return (
<Row className="breadcrumb-container"> <Row className="breadcrumb-container">
<Col xs={24} sm={24} md={16}> <Col xs={24} sm={24} md={16}>
<Breadcrumb separator=">"> <Breadcrumb separator=">">
<Breadcrumb.Item> <Breadcrumb.Item>
<Link to={`/manage`}> <Link to={`/manage`}>
<HomeFilled /> <HomeFilled />{" "}
{(bodyshop && bodyshop.shopname && `(${bodyshop.shopname})`) ||
""}
</Link> </Link>
</Breadcrumb.Item> </Breadcrumb.Item>
{breadcrumbs.map((item) => {breadcrumbs.map((item) =>

View File

@@ -64,9 +64,9 @@ export default function JobBillsTotalComponent({
}) })
); );
const totalPartsSublet = Dinero(totals.parts.parts.total).add( const totalPartsSublet = Dinero(totals.parts.parts.total)
Dinero(totals.parts.sublets.total) .add(Dinero(totals.parts.sublets.total))
); .add(Dinero(totals.additional.additionalCosts));
const discrepancy = totalPartsSublet.subtract(billTotals); const discrepancy = totalPartsSublet.subtract(billTotals);

View File

@@ -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 ( return (
<Card <Card
title={t("checklist.labels.checklist")} title={t("checklist.labels.checklist")}
@@ -195,21 +200,26 @@ export function JobChecklistForm({
addToProduction: true, addToProduction: true,
allow_text_message: job.owner && job.owner.allow_text_message, allow_text_message: job.owner && job.owner.allow_text_message,
scheduled_completion: scheduled_completion:
(job && job.scheduled_completion) || (job &&
(job.labbrs && job.larhrs job.scheduled_completion &&
? moment().businessAdd( moment(job.scheduled_completion)) ||
(job.labhrs.aggregate.sum.mod_lb_hrs + (job &&
job.larhrs.aggregate.sum.mod_lb_hrs) / job.labhrs &&
bodyshop.target_touchtime, job.larhrs &&
"days" moment().businessAdd(
) (job.labhrs.aggregate.sum.mod_lb_hrs ||
: null), 0 + job.larhrs.aggregate.sum.mod_lb_hrs ||
scheduled_delivery: job && job.scheduled_delivery, 0) / bodyshop.target_touchtime,
"days"
)),
scheduled_delivery: job && moment(job.scheduled_delivery),
}), }),
...(type === "deliver" && { ...(type === "deliver" && {
removeFromProduction: true, removeFromProduction: true,
actual_completion: job && job.actual_completion, actual_completion:
actual_delivery: job && job.actual_delivery, job && job.actual_completion && moment(job.actual_completion),
actual_delivery:
job && job.actual_delivery && moment(job.actual_delivery),
}), }),
...formItems ...formItems
.filter((fi) => fi.value) .filter((fi) => fi.value)

View File

@@ -18,7 +18,8 @@ export default function JobReconciliationModalComponent({ job, bills }) {
.flat() || []; .flat() || [];
const jobLineData = job.joblines.filter( 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 ( return (
@@ -50,3 +51,20 @@ export default function JobReconciliationModalComponent({ job, bills }) {
</div> </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
);
}

View File

@@ -8,9 +8,6 @@ import { DateFormatter } from "../../utils/DateFormatter";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
const OneCalendarDay = 60 * 60 * 24 * 1000;
const Now = new Date();
export default function ProductionListDate({ record, field, time }) { export default function ProductionListDate({ record, field, time }) {
const [updateAlert] = useMutation(UPDATE_JOB); const [updateAlert] = useMutation(UPDATE_JOB);
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
@@ -72,17 +69,13 @@ export default function ProductionListDate({ record, field, time }) {
style={{ style={{
height: "19px", height: "19px",
}} }}
className={
!!record[field] && moment().isSame(moment(record[field]), "day")
? "production-completion-1"
: ""
}
> >
<DateFormatter <DateFormatter bordered={false}>{record[field]}</DateFormatter>
bordered={false}
className={
!!record[field] && new Date(record[field]) - Now < OneCalendarDay
? "production-completion-1"
: ""
}
>
{record[field]}
</DateFormatter>
</div> </div>
</Dropdown> </Dropdown>
</div> </div>

View File

@@ -262,6 +262,9 @@ export const QUERY_DELIVER_CHECKLIST = gql`
jobs_by_pk(id: $jobId) { jobs_by_pk(id: $jobId) {
id id
ro_number ro_number
actual_completion
actual_delivery
} }
} }
`; `;

View File

@@ -69,6 +69,7 @@ export function JobsDeliverContainer({
checklistConfig={ checklistConfig={
(data && data.bodyshops_by_pk.deliverchecklist) || {} (data && data.bodyshops_by_pk.deliverchecklist) || {}
} }
job={data ? data.jobs_by_pk : {}}
/> />
</div> </div>
</RbacWrapper> </RbacWrapper>

View File

@@ -1030,6 +1030,7 @@ exports.QUERY_JOB_COSTING_DETAILS = ` query QUERY_JOB_COSTING_DETAILS($id: uuid!
status status
ca_bc_pvrt ca_bc_pvrt
ca_customer_gst ca_customer_gst
dms_allocation
joblines(where: { removed: { _eq: false } }) { joblines(where: { removed: { _eq: false } }) {
id id
db_ref db_ref
@@ -1073,11 +1074,14 @@ exports.QUERY_JOB_COSTING_DETAILS = ` query QUERY_JOB_COSTING_DETAILS($id: uuid!
actualhrs actualhrs
productivehrs productivehrs
flat_rate flat_rate
ciecacode
} }
bodyshop{ bodyshop{
id id
md_responsibility_centers md_responsibility_centers
jc_hourly_rates jc_hourly_rates
cdk_dealerid
pbs_serialnumber
} }
} }
}`; }`;
@@ -1133,6 +1137,7 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT
status status
ca_bc_pvrt ca_bc_pvrt
ca_customer_gst ca_customer_gst
dms_allocation
joblines(where: {removed: {_eq: false}}) { joblines(where: {removed: {_eq: false}}) {
id id
db_ref db_ref
@@ -1176,11 +1181,14 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT
actualhrs actualhrs
productivehrs productivehrs
flat_rate flat_rate
ciecacode
} }
bodyshop { bodyshop {
id id
md_responsibility_centers md_responsibility_centers
jc_hourly_rates jc_hourly_rates
cdk_dealerid
pbs_serialnumber
} }
} }
} }

View File

@@ -261,7 +261,7 @@ function GenerateCostingData(job) {
val.profitcenter_labor || defaultProfits[val.mod_lbr_ty] || "?"; val.profitcenter_labor || defaultProfits[val.mod_lbr_ty] || "?";
if (laborProfitCenter === "?") 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 rateName = `rate_${(val.mod_lbr_ty || "").toLowerCase()}`;
const laborAmount = Dinero({ const laborAmount = Dinero({
@@ -285,11 +285,12 @@ function GenerateCostingData(job) {
val.profitcenter_part || defaultProfits[val.part_type] || "?"; val.profitcenter_part || defaultProfits[val.part_type] || "?";
if (partsProfitCenter === "?") if (partsProfitCenter === "?")
console.log("Unknown type", val.part_type); console.log("Unknown type", val.line_desc, val.part_type);
if (!partsProfitCenter) if (!partsProfitCenter)
console.log( console.log(
"Unknown cost/profit center mapping for parts.", "Unknown cost/profit center mapping for parts.",
val.line_desc,
val.part_type val.part_type
); );
const partsAmount = Dinero({ const partsAmount = Dinero({
@@ -298,13 +299,13 @@ function GenerateCostingData(job) {
.multiply(val.part_qty || 1) .multiply(val.part_qty || 1)
.add( .add(
val.prt_dsmk_m && val.prt_dsmk_m !== 0 val.prt_dsmk_m && val.prt_dsmk_m !== 0
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
: Dinero({ : Dinero({
amount: Math.round(val.act_price * 100), amount: Math.round(val.act_price * 100),
}) })
.multiply(val.part_qty || 0) .multiply(val.part_qty || 0)
.percentage(Math.abs(val.prt_dsmk_p || 0)) .percentage(Math.abs(val.prt_dsmk_p || 0))
.multiply(val.prt_dsmk_p > 0 ? 1 : -1) .multiply(val.prt_dsmk_p > 0 ? 1 : -1)
); );
if (!acc.parts[partsProfitCenter]) if (!acc.parts[partsProfitCenter])
acc.parts[partsProfitCenter] = Dinero(); acc.parts[partsProfitCenter] = Dinero();
@@ -322,7 +323,7 @@ function GenerateCostingData(job) {
"?"; "?";
if (partsProfitCenter === "?") { if (partsProfitCenter === "?") {
console.log("Unknown type", val.part_type); console.log("Unknown type", val.line_desc, val.part_type);
} else { } else {
const partsAmount = Dinero({ const partsAmount = Dinero({
amount: Math.round((val.act_price || 0) * 100), amount: Math.round((val.act_price || 0) * 100),
@@ -330,13 +331,13 @@ function GenerateCostingData(job) {
.multiply(val.part_qty || 1) .multiply(val.part_qty || 1)
.add( .add(
val.prt_dsmk_m && val.prt_dsmk_m !== 0 val.prt_dsmk_m && val.prt_dsmk_m !== 0
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
: Dinero({ : Dinero({
amount: Math.round(val.act_price * 100), amount: Math.round(val.act_price * 100),
}) })
.multiply(val.part_qty || 0) .multiply(val.part_qty || 0)
.percentage(Math.abs(val.prt_dsmk_p || 0)) .percentage(Math.abs(val.prt_dsmk_p || 0))
.multiply(val.prt_dsmk_p > 0 ? 1 : -1) .multiply(val.prt_dsmk_p > 0 ? 1 : -1)
); );
if (!acc.parts[partsProfitCenter]) 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) => { const billTotalsByCostCenters = job.bills.reduce((bill_acc, bill_val) => {
//At the bill level. //At the bill level.
bill_val.billlines.map((line_val) => { bill_val.billlines.map((line_val) => {
//At the bill line level. //At the bill line level.
//console.log("JobCostingPartsTable -> line_val", line_val); if (job.bodyshop.pbs_serialnumber || job.bodyshop.cdk_dealerid) {
if (!bill_acc[line_val.cost_center]) if (!bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]])
bill_acc[line_val.cost_center] = Dinero(); bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] =
Dinero();
bill_acc[line_val.cost_center] = bill_acc[line_val.cost_center].add( bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] =
Dinero({ bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]].add(
amount: Math.round((line_val.actual_cost || 0) * 100), Dinero({
}) amount: Math.round((line_val.actual_cost || 0) * 100),
.multiply(line_val.quantity) })
.multiply(bill_val.is_credit_memo ? -1 : 1) .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; return null;
}); });
@@ -443,20 +464,38 @@ function GenerateCostingData(job) {
const ticketTotalsByCostCenter = job.timetickets.reduce( const ticketTotalsByCostCenter = job.timetickets.reduce(
(ticket_acc, ticket_val) => { (ticket_acc, ticket_val) => {
//At the invoice level. //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[ if (job.bodyshop.pbs_serialnumber || job.bodyshop.cdk_dealerid) {
ticket_val.cost_center if (!ticket_acc[selectedDmsAllocationConfig.costs[ticket_val.ciecacode]])
].add( ticket_acc[selectedDmsAllocationConfig.costs[ticket_val.ciecacode]] =
Dinero({ Dinero();
amount: Math.round((ticket_val.rate || 0) * 100),
}).multiply( ticket_acc[selectedDmsAllocationConfig.costs[ticket_val.ciecacode]] =
ticket_val.flat_rate ticket_acc[selectedDmsAllocationConfig.costs[ticket_val.ciecacode]].add(
? ticket_val.productivehrs || ticket_val.actualhrs || 0 Dinero({
: ticket_val.actualhrs || ticket_val.productivehrs || 0 amount: Math.round((ticket_val.rate || 0) * 100),
) //Should base this on the employee. }).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; return ticket_acc;
}, },