From 6d852453b0a21c07fb126ad2f6c8ac707bcd2c9e Mon Sep 17 00:00:00 2001 From: jfrye122 Date: Sun, 14 May 2023 11:59:34 -0400 Subject: [PATCH] added labor allocationtable --- components/Selects/select-cost-center.jsx | 5 +- .../labor-allocations-table.component.jsx | 215 +++++++++++++++--- .../screen-time-ticket-create.component.jsx | 44 ++-- graphql/jobs.queries.js | 48 ++++ translations/en-US/common.json | 90 ++++++++ translations/es-MX/common.json | 90 ++++++++ translations/fr-CA/common.json | 90 ++++++++ util/labor-allocations-table.utility.js | 54 +++++ 8 files changed, 591 insertions(+), 45 deletions(-) create mode 100644 util/labor-allocations-table.utility.js diff --git a/components/Selects/select-cost-center.jsx b/components/Selects/select-cost-center.jsx index 81abd44..136c076 100644 --- a/components/Selects/select-cost-center.jsx +++ b/components/Selects/select-cost-center.jsx @@ -77,12 +77,13 @@ export default connect(null, null)(CostCenterSelect); const styles = StyleSheet.create({ container: { - padding: 16, + marginVertical: 4, + marginHorizontal: 16, justifyContent: "center", alignContent: "center", }, dropdown: { - height: 50, + height: 48, borderColor: "gray", borderWidth: 0.5, borderRadius: 8, diff --git a/components/labor-allocations-table/labor-allocations-table.component.jsx b/components/labor-allocations-table/labor-allocations-table.component.jsx index b7ec0c5..e1096f3 100644 --- a/components/labor-allocations-table/labor-allocations-table.component.jsx +++ b/components/labor-allocations-table/labor-allocations-table.component.jsx @@ -1,23 +1,183 @@ -import React from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { FlatList, RefreshControl, StyleSheet, Text, View } from "react-native"; +import _ from "lodash"; import { Card, DataTable } from "react-native-paper"; +import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs.queries"; +import ErrorDisplay from "../error-display/error-display.component"; +import { useQuery } from "@apollo/client"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { selectCurrentEmployee } from "../../redux/employee/employee.selectors"; +import { CalculateAllocationsTotals } from "../../util/labor-allocations-table.utility"; -export default function LaborAllocationsTable({ job, loading, refetch }) { - const { t } = useTranslation(); - - if (!job) { - - Job is not defined. - ; - } - const onRefresh = async () => { - return refetch(); - }; - - return ( - - +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, + technician: selectCurrentEmployee, +}); + +export function LaborAllocationsTable({ jobId, bodyshop, technician }) { + console.log("LaborAllocationsTable, jobId", jobId); + //, loading, refetch + //const jobid = jobid !== undefined ? jobid : ""; + const { t } = useTranslation(); + + const onRefresh = async () => { + + console.log("LaborAllocationsTable refetch"); + return refetch(); + }; + + //maybe use this + const { loading, error, data, refetch } = useQuery(GET_LINE_TICKET_BY_PK, { + variables: { id: jobId }, + skip: !!!jobId, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + }); + + // console.log("LaborAllocationsTable, data", data); + if (error) return ; + + // let joblines = []; + // let timetickets = []; + // let adjustments = []; + + const [totals, setTotals] = useState([]); + const [state, setState] = useState({ + sortedInfo: { + columnKey: "cost_center", + field: "cost_center", + order: "ascend", + }, + filteredInfo: {}, + }); + + useEffect(() => { + console.log("LaborAllocationsTable useEffect on data change"); + // joblines = data?.joblines ? data.joblines : []; + // timetickets = data?.timetickets ? data.timetickets : []; + // adjustments = data?.adjustments ? data.adjustments : []; + }, [data]); + + useEffect(() => { + console.log( + "LaborAllocationsTable useEffect on [joblines, timetickets, bodyshop, adjustments, jobId] change", + data?.joblines, + data?.adjustments + ); + if (!!data?.joblines && !!data?.timetickets && !!bodyshop) + setTotals( + CalculateAllocationsTotals( + bodyshop, + data?.joblines, + data?.timetickets, + data?.adjustments + ) + ); + if (!jobId) setTotals([]); + }, [data?.joblines, data?.timetickets, bodyshop, data?.adjustments, jobId]); + + // const convertedLines = useMemo( + // () => data?.joblines && data?.joblines.filter((j) => j.convertedtolbr), + // [data?.joblines] + // ); + + const summary = + totals && + totals.reduce( + (acc, val) => { + acc.hrs_total += val.total; + acc.hrs_claimed += val.claimed; + acc.adjustments += val.adjustments; + acc.difference += val.difference; + return acc; + }, + { hrs_total: 0, hrs_claimed: 0, adjustments: 0, difference: 0 } + ); + + console.log("labor summary is:", summary); + + return ( + + {typeof data !== "undefined" ? ( + + + + Cost Center + + Hours Total + + + Hours Claimed + + + Adjustments + + + Difference + + + + + + } + keyExtractor={(item) => item.cost_center} + renderItem={(object) => ( + + + + + {object.item.cost_center} + {object.item.mod_lbr_ty} + + + + {object.item.total && object.item.total.toFixed(1)} + + + {object.item.claimed && object.item.claimed.toFixed(1)} + + + + {object.item.adjustments && object.item.adjustments.toFixed(1)} + {/* {!technician && ( + + + + )} */} + + + + = 0 ? "green" : "red", + }}> + {/* = 0 ? "green" : "red", + }} + > + {_.round(object.difference, 1)} + */} + {_.round(object.item.difference, 1)} + + + + )} + /> + + + ) : null} + {/* {t("jobdetail.labels.lines_desc")} @@ -35,9 +195,9 @@ export default function LaborAllocationsTable({ job, loading, refetch }) { {t("jobdetail.labels.lines_qty")} - - - */} + + {/* @@ -64,10 +224,13 @@ export default function LaborAllocationsTable({ job, loading, refetch }) { )} - /> - - ); - } - - const localStyles = StyleSheet.create({}); - \ No newline at end of file + /> */} + + {/* use "totals" for the rows in the table */} + {/* use "summary" for the totals at the bottom */} + + ); +} + +const localStyles = StyleSheet.create({}); +export default connect(mapStateToProps, null)(LaborAllocationsTable); diff --git a/components/time-ticket/screen-time-ticket-create.component.jsx b/components/time-ticket/screen-time-ticket-create.component.jsx index b016ab0..3d051ac 100644 --- a/components/time-ticket/screen-time-ticket-create.component.jsx +++ b/components/time-ticket/screen-time-ticket-create.component.jsx @@ -14,6 +14,8 @@ import { } from "../../redux/employee/employee.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors"; import { useCallback } from "react"; +// import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component"; +import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component"; //TODO add props needed for call const mapStateToProps = createStructuredSelector({ @@ -34,15 +36,15 @@ export function TimeTicketCreate({ const [date2, setDate2] = useState(new Date()); const [currentSCC, setCurrentSCC] = useState(null); - const [currentSJob, setCurrentSJob] = useState(null); + // const [currentSJob, setCurrentSJob] = useState(null); const [currentSJobId, setCurrentSJobId] = useState(null); - const wrapperSetCurrentSJobState = useCallback( - (val) => { - setCurrentSJob(val); - }, - [setCurrentSJob] - ); + // const wrapperSetCurrentSJobState = useCallback( + // (val) => { + // setCurrentSJob(val); + // }, + // [setCurrentSJob] + // ); const showDatePicker = () => { setDatePickerVisibility(true); @@ -56,19 +58,20 @@ export function TimeTicketCreate({ hideDatePicker(); }; const formSubmit = (values) => { - Dialog.alert({ content:
{JSON.stringify(values, null, 2)}
}); + console.log("values", values); + //Dialog.alert({ content:
{JSON.stringify(values, null, 2)}
}); //TODO update with start call for create time ticket }; return ( - + {/* Below is for list of jobs/tickets */} - + + + + + + ); } @@ -159,12 +167,13 @@ const localStyles = StyleSheet.create({ display: "flex", flex: 1, }, - topTimeTicketContainer: {}, - bottomTimeTicketContainer: {}, + topContainer: {}, + bottomContainer: {}, input: {}, dateButton: { - margin: 16, - height: 40, + marginVertical: 4, + marginHorizontal: 16, + height: 48, justifyContent: "center", alignContent: "center", borderColor: "blue", @@ -177,7 +186,8 @@ const localStyles = StyleSheet.create({ alignContent: "center", }, inputStyle: { - margin: 16, + marginVertical: 4, + marginHorizontal: 16, height: 48, fontSize: 16, }, diff --git a/graphql/jobs.queries.js b/graphql/jobs.queries.js index d56fa26..22adc17 100644 --- a/graphql/jobs.queries.js +++ b/graphql/jobs.queries.js @@ -923,6 +923,54 @@ export const QUERY_JOB_CLOSE_DETAILS = gql` } `; +export const GET_LINE_TICKET_BY_PK = gql` + query GET_LINE_TICKET_BY_PK($id: uuid!) { + jobs_by_pk(id: $id) { + id + lbr_adjustments + converted + } + joblines(where: { jobid: { _eq: $id }, removed: { _eq: false } }) { + id + line_desc + 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 + convertedtolbr + convertedtolbr_data + } + timetickets(where: { jobid: { _eq: $id } }) { + actualhrs + ciecacode + cost_center + date + id + jobid + employeeid + memo + flat_rate + clockon + clockoff + rate + employee { + id + first_name + last_name + employee_number + } + productivehrs + } + } +`; + export const generate_UPDATE_JOB_KANBAN = ( oldChildId, oldChildNewParent, diff --git a/translations/en-US/common.json b/translations/en-US/common.json index 7929c3b..c3af9b5 100644 --- a/translations/en-US/common.json +++ b/translations/en-US/common.json @@ -360,6 +360,96 @@ "titles": { "createtimeticket": "Create a Time Ticket" } + }, + "joblines": { + "actions": { + "converttolabor": "Convert amount to Labor.", + "new": "New Line" + }, + "errors": { + "creating": "Error encountered while creating job line. {{message}}", + "updating": "Error encountered updating job line. {{message}}" + }, + "fields": { + "act_price": "Retail Price", + "ah_detail_line": "Mark as Detail Labor Line (Autohouse Only)", + "db_price": "List Price", + "lbr_types": { + "LA1": "LA1", + "LA2": "LA2", + "LA3": "LA3", + "LA4": "LA4", + "LAA": "Aluminum", + "LAB": "Body", + "LAD": "Diagnostic", + "LAE": "Electrical", + "LAF": "Frame", + "LAG": "Glass", + "LAM": "Mechanical", + "LAR": "Refinish", + "LAS": "Structural", + "LAU": "User Defined" + }, + "line_desc": "Line Desc.", + "line_ind": "S#", + "line_no": "Line #", + "location": "Location", + "mod_lb_hrs": "Hrs", + "mod_lbr_ty": "Labor Type", + "notes": "Notes", + "oem_partno": "OEM Part #", + "op_code_desc": "Op Code Description", + "part_qty": "Qty.", + "part_type": "Part Type", + "part_types": { + "CCC": "CC Cleaning", + "CCD": "CC Damage Waiver", + "CCDR": "CC Daily Rate", + "CCF": "CC Refuel", + "CCM": "CC Mileage", + "PAA": "Aftermarket", + "PAC": "Rechromed", + "PAE": "Existing", + "PAG": "Glass", + "PAL": "LKQ", + "PAM": "Remanufactured", + "PAN": "New/OEM", + "PAO": "Other", + "PAP": "OEM Partial", + "PAR": "Recored", + "PAS": "Sublet", + "PASL": "Sublet (L)" + }, + "profitcenter_labor": "Profit Center: Labor", + "profitcenter_part": "Profit Center: Part", + "prt_dsmk_m": "Line Discount/Markup $", + "prt_dsmk_p": "Line Discount/Markup %", + "status": "Status", + "tax_part": "Tax Part", + "total": "Total", + "unq_seq": "Seq #" + }, + "labels": { + "adjustmenttobeadded": "Adjustment to be added: {{adjustment}}", + "billref": "Latest Bill", + "convertedtolabor": "This line has been converted to labor. Ensure you adjust the profit center for the amount accordingly.", + "edit": "Edit Line", + "ioucreated": "IOU", + "new": "New Line", + "nostatus": "No Status", + "presets": "Jobline Presets" + }, + "successes": { + "created": "Job line created successfully.", + "saved": "Job line saved.", + "updated": "Job line updated successfully." + }, + "validations": { + "ahdetailonlyonuserdefinedtypes": "Detail line indicator can only be set for LA1, LA2, LA3, LA4, and LAU labor types.", + "hrsrequirediflbrtyp": "Labor hours are required if a labor type is selected. Clear the labor type if there are no labor hours.", + "requiredifparttype": "Required if a part type has been specified.", + "zeropriceexistingpart": "This line cannot have any price since it uses an existing part." + } } } } diff --git a/translations/es-MX/common.json b/translations/es-MX/common.json index 88a4f1a..0d4aac5 100644 --- a/translations/es-MX/common.json +++ b/translations/es-MX/common.json @@ -360,6 +360,96 @@ "titles": { "createtimeticket": "" } + }, + "joblines": { + "actions": { + "converttolabor": "", + "new": "" + }, + "errors": { + "creating": "", + "updating": "" + }, + "fields": { + "act_price": "Precio actual", + "ah_detail_line": "", + "db_price": "Precio de base de datos", + "lbr_types": { + "LA1": "", + "LA2": "", + "LA3": "", + "LA4": "", + "LAA": "", + "LAB": "", + "LAD": "", + "LAE": "", + "LAF": "", + "LAG": "", + "LAM": "", + "LAR": "", + "LAS": "", + "LAU": "" + }, + "line_desc": "Descripción de línea", + "line_ind": "S#", + "line_no": "", + "location": "", + "mod_lb_hrs": "Horas laborales", + "mod_lbr_ty": "Tipo de trabajo", + "notes": "", + "oem_partno": "OEM parte #", + "op_code_desc": "", + "part_qty": "", + "part_type": "Tipo de parte", + "part_types": { + "CCC": "", + "CCD": "", + "CCDR": "", + "CCF": "", + "CCM": "", + "PAA": "", + "PAC": "", + "PAE": "", + "PAG": "", + "PAL": "", + "PAM": "", + "PAN": "", + "PAO": "", + "PAP": "", + "PAR": "", + "PAS": "", + "PASL": "" + }, + "profitcenter_labor": "", + "profitcenter_part": "", + "prt_dsmk_m": "", + "prt_dsmk_p": "", + "status": "Estado", + "tax_part": "", + "total": "", + "unq_seq": "Seq #" + }, + "labels": { + "adjustmenttobeadded": "", + "billref": "", + "convertedtolabor": "", + "edit": "Línea de edición", + "ioucreated": "", + "new": "Nueva línea", + "nostatus": "", + "presets": "" + }, + "successes": { + "created": "", + "saved": "", + "updated": "" + }, + "validations": { + "ahdetailonlyonuserdefinedtypes": "", + "hrsrequirediflbrtyp": "", + "requiredifparttype": "", + "zeropriceexistingpart": "" + } } } } diff --git a/translations/fr-CA/common.json b/translations/fr-CA/common.json index a1d1cad..efb30bd 100644 --- a/translations/fr-CA/common.json +++ b/translations/fr-CA/common.json @@ -360,6 +360,96 @@ "titles": { "createtimeticket": "" } + }, + "joblines": { + "actions": { + "converttolabor": "", + "new": "" + }, + "errors": { + "creating": "", + "updating": "" + }, + "fields": { + "act_price": "Prix actuel", + "ah_detail_line": "", + "db_price": "Prix de la base de données", + "lbr_types": { + "LA1": "", + "LA2": "", + "LA3": "", + "LA4": "", + "LAA": "", + "LAB": "", + "LAD": "", + "LAE": "", + "LAF": "", + "LAG": "", + "LAM": "", + "LAR": "", + "LAS": "", + "LAU": "" + }, + "line_desc": "Description de la ligne", + "line_ind": "S#", + "line_no": "", + "location": "", + "mod_lb_hrs": "Heures de travail", + "mod_lbr_ty": "Type de travail", + "notes": "", + "oem_partno": "Pièce OEM #", + "op_code_desc": "", + "part_qty": "", + "part_type": "Type de pièce", + "part_types": { + "CCC": "", + "CCD": "", + "CCDR": "", + "CCF": "", + "CCM": "", + "PAA": "", + "PAC": "", + "PAE": "", + "PAG": "", + "PAL": "", + "PAM": "", + "PAN": "", + "PAO": "", + "PAP": "", + "PAR": "", + "PAS": "", + "PASL": "" + }, + "profitcenter_labor": "", + "profitcenter_part": "", + "prt_dsmk_m": "", + "prt_dsmk_p": "", + "status": "Statut", + "tax_part": "", + "total": "", + "unq_seq": "Seq #" + }, + "labels": { + "adjustmenttobeadded": "", + "billref": "", + "convertedtolabor": "", + "edit": "Ligne d'édition", + "ioucreated": "", + "new": "Nouvelle ligne", + "nostatus": "", + "presets": "" + }, + "successes": { + "created": "", + "saved": "", + "updated": "" + }, + "validations": { + "ahdetailonlyonuserdefinedtypes": "", + "hrsrequirediflbrtyp": "", + "requiredifparttype": "", + "zeropriceexistingpart": "" + } } } } diff --git a/util/labor-allocations-table.utility.js b/util/labor-allocations-table.utility.js new file mode 100644 index 0000000..368e2cd --- /dev/null +++ b/util/labor-allocations-table.utility.js @@ -0,0 +1,54 @@ +import i18next from "i18next"; +import React from "react"; + +export const CalculateAllocationsTotals = ( + bodyshop, + joblines, + timetickets, + adjustments = [] +) => { +// console.log("🚀 ~ file: adjustments", adjustments); +// console.log("🚀 ~ file: bodyshop", bodyshop); + console.log("🚀 ~ file: joblines", joblines); +// console.log("🚀 ~ file: timetickets", timetickets); + const responsibilitycenters = bodyshop.md_responsibility_centers; + const jobCodes = joblines.map((item) => item.mod_lbr_ty); + console.log("jobCodes :", jobCodes); + //.filter((value, index, self) => self.indexOf(value) === index && !!value); + const ticketCodes = timetickets.map((item) => item.ciecacode); + console.log("ticketCodes :", ticketCodes); + //.filter((value, index, self) => self.indexOf(value) === index && !!value); + const adjustmentCodes = Object.keys(adjustments); + console.log("adjustmentCodes :", adjustmentCodes); + const allCodes = [...jobCodes, ...ticketCodes, ...adjustmentCodes].filter( + (value, index, self) => self.indexOf(value) === index && !!value + ); + console.log("allCodes :", allCodes); + + const r = allCodes.reduce((acc, value) => { + const r = { + opcode: value, + cost_center: + bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber + ? i18next.t( + `joblines.fields.lbr_types.${value && value.toUpperCase()}` + ) + : responsibilitycenters.defaults.costs[value], + mod_lbr_ty: value, + total: joblines.reduce((acc2, val2) => { + return val2.mod_lbr_ty === value ? acc2 + val2.mod_lb_hrs : acc2; + }, 0), + adjustments: adjustments[value] || 0, + claimed: timetickets.reduce((acc3, val3) => { + return val3.ciecacode === value ? acc3 + val3.productivehrs : acc3; + }, 0), + }; + + r.difference = r.total + r.adjustments - r.claimed; + acc.push(r); + return acc; + }, []); + console.log(" r is :", r); + + return r; +};